1
0
Fork 0

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
hifive-unleashed-5.1
Linus Torvalds 2006-12-13 09:13:19 -08:00
commit bbc7610c06
30 changed files with 4115 additions and 300 deletions

View File

@ -151,15 +151,6 @@ Who: Thomas Gleixner <tglx@linutronix.de>
---------------------------
What: I2C interface of the it87 driver
When: January 2007
Why: The ISA interface is faster and should be always available. The I2C
probing is also known to cause trouble in at least one case (see
bug #5889.)
Who: Jean Delvare <khali@linux-fr.org>
---------------------------
What: Unused EXPORT_SYMBOL/EXPORT_SYMBOL_GPL exports
(temporary transition config option provided until then)
The transition config option will also be removed at the same time.

View File

@ -6,6 +6,10 @@ Supported chips:
Prefix: 'f71805f'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Provided by Fintek on request
* Fintek F71872F/FG
Prefix: 'f71872f'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Provided by Fintek on request
Author: Jean Delvare <khali@linux-fr.org>
@ -13,8 +17,8 @@ Thanks to Denis Kieft from Barracuda Networks for the donation of a
test system (custom Jetway K8M8MS motherboard, with CPU and RAM) and
for providing initial documentation.
Thanks to Kris Chen from Fintek for answering technical questions and
providing additional documentation.
Thanks to Kris Chen and Aaron Huang from Fintek for answering technical
questions and providing additional documentation.
Thanks to Chris Lin from Jetway for providing wiring schematics and
answering technical questions.
@ -28,8 +32,11 @@ capabilities. It can monitor up to 9 voltages (counting its own power
source), 3 fans and 3 temperature sensors.
This chip also has fan controlling features, using either DC or PWM, in
three different modes (one manual, two automatic). The driver doesn't
support these features yet.
three different modes (one manual, two automatic).
The Fintek F71872F/FG Super I/O chip is almost the same, with two
additional internal voltages monitored (VSB and battery). It also features
6 VID inputs. The VID inputs are not yet supported by this driver.
The driver assumes that no more than one chip is present, which seems
reasonable.
@ -42,7 +49,8 @@ Voltages are sampled by an 8-bit ADC with a LSB of 8 mV. The supported
range is thus from 0 to 2.040 V. Voltage values outside of this range
need external resistors. An exception is in0, which is used to monitor
the chip's own power source (+3.3V), and is divided internally by a
factor 2.
factor 2. For the F71872F/FG, in9 (VSB) and in10 (battery) are also
divided internally by a factor 2.
The two LSB of the voltage limit registers are not used (always 0), so
you can only set the limits in steps of 32 mV (before scaling).
@ -61,9 +69,12 @@ in5 VIN5 +12V 200K 20K 11.00 1.05 V
in6 VIN6 VCC1.5V 10K - 1.00 1.50 V
in7 VIN7 VCORE 10K - 1.00 ~1.40 V (1)
in8 VIN8 VSB5V 200K 47K 1.00 0.95 V
in10 VSB VSB3.3V int. int. 2.00 1.65 V (3)
in9 VBAT VBATTERY int. int. 2.00 1.50 V (3)
(1) Depends on your hardware setup.
(2) Obviously not correct, swapping R1 and R2 would make more sense.
(3) F71872F/FG only.
These values can be used as hints at best, as motherboard manufacturers
are free to use a completely different setup. As a matter of fact, the
@ -103,3 +114,38 @@ sensor. Each channel can be used for connecting either a thermal diode
or a thermistor. The driver reports the currently selected mode, but
doesn't allow changing it. In theory, the BIOS should have configured
everything properly.
Fan Control
-----------
Both PWM (pulse-width modulation) and DC fan speed control methods are
supported. The right one to use depends on external circuitry on the
motherboard, so the driver assumes that the BIOS set the method
properly. The driver will report the method, but won't let you change
it.
When the PWM method is used, you can select the operating frequency,
from 187.5 kHz (default) to 31 Hz. The best frequency depends on the
fan model. As a rule of thumb, lower frequencies seem to give better
control, but may generate annoying high-pitch noise. Fintek recommends
not going below 1 kHz, as the fan tachometers get confused by lower
frequencies as well.
When the DC method is used, Fintek recommends not going below 5 V, which
corresponds to a pwm value of 106 for the driver. The driver doesn't
enforce this limit though.
Three different fan control modes are supported:
* Manual mode
You ask for a specific PWM duty cycle or DC voltage.
* Fan speed mode
You ask for a specific fan speed. This mode assumes that pwm1
corresponds to fan1, pwm2 to fan2 and pwm3 to fan3.
* Temperature mode
You define 3 temperature/fan speed trip points, and the fan speed is
adjusted depending on the measured temperature, using interpolation.
This mode is not yet supported by the driver.

View File

@ -9,8 +9,7 @@ Supported chips:
http://www.ite.com.tw/
* IT8712F
Prefix: 'it8712'
Addresses scanned: I2C 0x2d
from Super I/O config space (8 I/O ports)
Addresses scanned: from Super I/O config space (8 I/O ports)
Datasheet: Publicly available at the ITE website
http://www.ite.com.tw/
* IT8716F
@ -53,6 +52,18 @@ Module Parameters
misconfigured by BIOS - PWM values would be inverted. This option tries
to fix this. Please contact your BIOS manufacturer and ask him for fix.
Hardware Interfaces
-------------------
All the chips suported by this driver are LPC Super-I/O chips, accessed
through the LPC bus (ISA-like I/O ports). The IT8712F additionally has an
SMBus interface to the hardware monitoring functions. This driver no
longer supports this interface though, as it is slower and less reliable
than the ISA access, and was only available on a small number of
motherboard models.
Description
-----------

View File

@ -8,7 +8,7 @@ Supported chips:
Datasheet: http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf
Author: Rudolf Marek
Contact: Rudolf Marek <r.marek@sh.cvut.cz>
Contact: Rudolf Marek <r.marek@assembler.cz>
Description
-----------

View 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).

View File

@ -208,12 +208,14 @@ temp[1-*]_auto_point[1-*]_temp_hyst
****************
temp[1-*]_type Sensor type selection.
Integers 1 to 4 or thermistor Beta value (typically 3435)
Integers 1 to 6 or thermistor Beta value (typically 3435)
RW
1: PII/Celeron Diode
2: 3904 transistor
3: thermal diode
4: thermistor (default/unknown Beta)
5: AMD AMDSI
6: Intel PECI
Not all types are supported by all chips
temp[1-*]_max Temperature max value.

View File

@ -10,7 +10,7 @@ Supported chips:
Authors:
Jean Delvare <khali@linux-fr.org>
Yuan Mu (Winbond)
Rudolf Marek <r.marek@sh.cvut.cz>
Rudolf Marek <r.marek@assembler.cz>
Description
-----------

View File

@ -18,7 +18,7 @@ Credits:
and Mark Studebaker <mdsxyz123@yahoo.com>
w83792d.c:
Chunhao Huang <DZShen@Winbond.com.tw>,
Rudolf Marek <r.marek@sh.cvut.cz>
Rudolf Marek <r.marek@assembler.cz>
Additional contributors:
Sven Anders <anders@anduras.de>

View 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.

View File

@ -277,7 +277,7 @@ S: Maintained
ALI1563 I2C DRIVER
P: Rudolf Marek
M: r.marek@sh.cvut.cz
M: r.marek@assembler.cz
L: i2c@lm-sensors.org
S: Maintained
@ -296,6 +296,13 @@ L: info-linux@geode.amd.com
W: http://www.amd.com/us-en/ConnectivitySolutions/TechnicalResources/0,,50_2334_2452_11363,00.html
S: Supported
AMS (Apple Motion Sensor) DRIVER
P: Stelian Pop
M: stelian@popies.net
P: Michael Hanselmann
M: linux-kernel@hansmi.ch
S: Supported
AMSO1100 RNIC DRIVER
P: Tom Tucker
M: tom@opengridcomputing.com
@ -3436,6 +3443,12 @@ M: bezaur@gmail.com
L: lm-sensors@lm-sensors.org
S: Maintained
W83793 HARDWARE MONITORING DRIVER
P: Rudolf Marek
M: r.marek@assembler.cz
L: lm-sensors@lm-sensors.org
S: Maintained
W83L51xD SD/MMC CARD INTERFACE DRIVER
P: Pierre Ossman
M: drzeus-wbsd@drzeus.cx

View File

@ -106,6 +106,31 @@ config SENSORS_K8TEMP
This driver can also be built as a module. If so, the module
will be called k8temp.
config SENSORS_AMS
tristate "Apple Motion Sensor driver"
depends on HWMON && PPC_PMAC && !PPC64 && INPUT && ((ADB_PMU && I2C = y) || (ADB_PMU && !I2C) || I2C) && EXPERIMENTAL
help
Support for the motion sensor included in PowerBooks. Includes
implementations for PMU and I2C.
This driver can also be built as a module. If so, the module
will be called ams.
config SENSORS_AMS_PMU
bool "PMU variant"
depends on SENSORS_AMS && ADB_PMU
default y
help
PMU variant of motion sensor, found in late 2005 PowerBooks.
config SENSORS_AMS_I2C
bool "I2C variant"
depends on SENSORS_AMS && I2C
default y
help
I2C variant of motion sensor, found in early 2005 PowerBooks and
iBooks.
config SENSORS_ASB100
tristate "Asus ASB100 Bach"
depends on HWMON && I2C && EXPERIMENTAL
@ -142,11 +167,12 @@ config SENSORS_DS1621
will be called ds1621.
config SENSORS_F71805F
tristate "Fintek F71805F/FG"
tristate "Fintek F71805F/FG and F71872F/FG"
depends on HWMON && EXPERIMENTAL
help
If you say yes here you get support for hardware monitoring
features of the Fintek F71805F/FG chips.
features of the Fintek F71805F/FG and F71872F/FG Super-I/O
chips.
This driver can also be built as a module. If so, the module
will be called f71805f.
@ -353,6 +379,19 @@ config SENSORS_PC87360
This driver can also be built as a module. If so, the module
will be called pc87360.
config SENSORS_PC87427
tristate "National Semiconductor PC87427"
depends on HWMON && EXPERIMENTAL
help
If you say yes here you get access to the hardware monitoring
functions of the National Semiconductor PC87427 Super-I/O chip.
The chip has two distinct logical devices, one for fan speed
monitoring and control, and one for voltage and temperature
monitoring. Only fan speed monitoring is supported right now.
This driver can also be built as a module. If so, the module
will be called pc87427.
config SENSORS_SIS5595
tristate "Silicon Integrated Systems Corp. SiS5595"
depends on HWMON && I2C && PCI && EXPERIMENTAL
@ -474,6 +513,16 @@ config SENSORS_W83792D
This driver can also be built as a module. If so, the module
will be called w83792d.
config SENSORS_W83793
tristate "Winbond W83793"
depends on HWMON && I2C && EXPERIMENTAL
help
If you say yes here you get support for the Winbond W83793
hardware monitoring chip.
This driver can also be built as a module. If so, the module
will be called w83793.
config SENSORS_W83L785TS
tristate "Winbond W83L785TS-S"
depends on HWMON && I2C && EXPERIMENTAL
@ -527,6 +576,9 @@ config SENSORS_HDAPS
This driver also provides an absolute input class device, allowing
the laptop to act as a pinball machine-esque joystick.
If your ThinkPad is not recognized by the driver, please update to latest
BIOS. This is especially the case for some R52 ThinkPads.
Say Y here if you have an applicable laptop and want to experience
the awesome power of hdaps.

View File

@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o
obj-$(CONFIG_SENSORS_ASB100) += asb100.o
obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o
obj-$(CONFIG_SENSORS_W83792D) += w83792d.o
obj-$(CONFIG_SENSORS_W83793) += w83793.o
obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
@ -18,6 +19,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
obj-$(CONFIG_SENSORS_AMS) += ams/
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
@ -41,6 +43,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o
obj-$(CONFIG_SENSORS_LM92) += lm92.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o

View 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

View 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);

View 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;
}

View 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);
}

View 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;
}

View 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);

View File

@ -1,12 +1,15 @@
/*
* f71805f.c - driver for the Fintek F71805F/FG Super-I/O chip integrated
* hardware monitoring features
* f71805f.c - driver for the Fintek F71805F/FG and F71872F/FG Super-I/O
* chips integrated hardware monitoring features
* Copyright (C) 2005-2006 Jean Delvare <khali@linux-fr.org>
*
* The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates
* complete hardware monitoring features: voltage, fan and temperature
* sensors, and manual and automatic fan speed control.
*
* The F71872F/FG is almost the same, with two more voltages monitored,
* and 6 VID inputs.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@ -37,6 +40,7 @@
static struct platform_device *pdev;
#define DRVNAME "f71805f"
enum kinds { f71805f, f71872f };
/*
* Super-I/O constants and functions
@ -48,11 +52,13 @@ static struct platform_device *pdev;
#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
#define SIO_REG_DEVREV 0x22 /* Device revision */
#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
#define SIO_REG_FNSEL1 0x29 /* Multi Function Select 1 (F71872F) */
#define SIO_REG_ENABLE 0x30 /* Logical device enable */
#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
#define SIO_FINTEK_ID 0x1934
#define SIO_F71805F_ID 0x0406
#define SIO_F71872F_ID 0x0341
static inline int
superio_inb(int base, int reg)
@ -96,22 +102,25 @@ superio_exit(int base)
* ISA constants
*/
#define REGION_LENGTH 2
#define ADDR_REG_OFFSET 0
#define DATA_REG_OFFSET 1
#define REGION_LENGTH 8
#define ADDR_REG_OFFSET 5
#define DATA_REG_OFFSET 6
/*
* Registers
*/
/* in nr from 0 to 8 (8-bit values) */
/* in nr from 0 to 10 (8-bit values) */
#define F71805F_REG_IN(nr) (0x10 + (nr))
#define F71805F_REG_IN_HIGH(nr) (0x40 + 2 * (nr))
#define F71805F_REG_IN_LOW(nr) (0x41 + 2 * (nr))
#define F71805F_REG_IN_HIGH(nr) ((nr) < 10 ? 0x40 + 2 * (nr) : 0x2E)
#define F71805F_REG_IN_LOW(nr) ((nr) < 10 ? 0x41 + 2 * (nr) : 0x2F)
/* fan nr from 0 to 2 (12-bit values, two registers) */
#define F71805F_REG_FAN(nr) (0x20 + 2 * (nr))
#define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr))
#define F71805F_REG_FAN_TARGET(nr) (0x69 + 16 * (nr))
#define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr))
#define F71805F_REG_PWM_FREQ(nr) (0x63 + 16 * (nr))
#define F71805F_REG_PWM_DUTY(nr) (0x6B + 16 * (nr))
/* temp nr from 0 to 2 (8-bit values) */
#define F71805F_REG_TEMP(nr) (0x1B + (nr))
#define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr))
@ -122,6 +131,14 @@ superio_exit(int base)
/* status nr from 0 to 2 */
#define F71805F_REG_STATUS(nr) (0x36 + (nr))
/* individual register bits */
#define FAN_CTRL_DC_MODE 0x10
#define FAN_CTRL_LATCH_FULL 0x08
#define FAN_CTRL_MODE_MASK 0x03
#define FAN_CTRL_MODE_SPEED 0x00
#define FAN_CTRL_MODE_TEMPERATURE 0x01
#define FAN_CTRL_MODE_MANUAL 0x02
/*
* Data structures and manipulation thereof
*/
@ -138,12 +155,16 @@ struct f71805f_data {
unsigned long last_limits; /* In jiffies */
/* Register values */
u8 in[9];
u8 in_high[9];
u8 in_low[9];
u8 in[11];
u8 in_high[11];
u8 in_low[11];
u16 has_in;
u16 fan[3];
u16 fan_low[3];
u8 fan_enabled; /* Read once at init time */
u16 fan_target[3];
u8 fan_ctrl[3];
u8 pwm[3];
u8 pwm_freq[3];
u8 temp[3];
u8 temp_high[3];
u8 temp_hyst[3];
@ -151,6 +172,11 @@ struct f71805f_data {
unsigned long alarms;
};
struct f71805f_sio_data {
enum kinds kind;
u8 fnsel1;
};
static inline long in_from_reg(u8 reg)
{
return (reg * 8);
@ -200,6 +226,33 @@ static inline u16 fan_to_reg(long rpm)
return (1500000 / rpm);
}
static inline unsigned long pwm_freq_from_reg(u8 reg)
{
unsigned long clock = (reg & 0x80) ? 48000000UL : 1000000UL;
reg &= 0x7f;
if (reg == 0)
reg++;
return clock / (reg << 8);
}
static inline u8 pwm_freq_to_reg(unsigned long val)
{
if (val >= 187500) /* The highest we can do */
return 0x80;
if (val >= 1475) /* Use 48 MHz clock */
return 0x80 | (48000000UL / (val << 8));
if (val < 31) /* The lowest we can do */
return 0x7f;
else /* Use 1 MHz clock */
return 1000000UL / (val << 8);
}
static inline int pwm_mode_from_reg(u8 reg)
{
return !(reg & FAN_CTRL_DC_MODE);
}
static inline long temp_from_reg(u8 reg)
{
return (reg * 1000);
@ -274,16 +327,21 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
/* Limit registers cache is refreshed after 60 seconds */
if (time_after(jiffies, data->last_updated + 60 * HZ)
|| !data->valid) {
for (nr = 0; nr < 9; nr++) {
for (nr = 0; nr < 11; nr++) {
if (!(data->has_in & (1 << nr)))
continue;
data->in_high[nr] = f71805f_read8(data,
F71805F_REG_IN_HIGH(nr));
data->in_low[nr] = f71805f_read8(data,
F71805F_REG_IN_LOW(nr));
}
for (nr = 0; nr < 3; nr++) {
if (data->fan_enabled & (1 << nr))
data->fan_low[nr] = f71805f_read16(data,
F71805F_REG_FAN_LOW(nr));
data->fan_low[nr] = f71805f_read16(data,
F71805F_REG_FAN_LOW(nr));
data->fan_target[nr] = f71805f_read16(data,
F71805F_REG_FAN_TARGET(nr));
data->pwm_freq[nr] = f71805f_read8(data,
F71805F_REG_PWM_FREQ(nr));
}
for (nr = 0; nr < 3; nr++) {
data->temp_high[nr] = f71805f_read8(data,
@ -299,14 +357,19 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
/* Measurement registers cache is refreshed after 1 second */
if (time_after(jiffies, data->last_updated + HZ)
|| !data->valid) {
for (nr = 0; nr < 9; nr++) {
for (nr = 0; nr < 11; nr++) {
if (!(data->has_in & (1 << nr)))
continue;
data->in[nr] = f71805f_read8(data,
F71805F_REG_IN(nr));
}
for (nr = 0; nr < 3; nr++) {
if (data->fan_enabled & (1 << nr))
data->fan[nr] = f71805f_read16(data,
F71805F_REG_FAN(nr));
data->fan[nr] = f71805f_read16(data,
F71805F_REG_FAN(nr));
data->fan_ctrl[nr] = f71805f_read8(data,
F71805F_REG_FAN_CTRL(nr));
data->pwm[nr] = f71805f_read8(data,
F71805F_REG_PWM_DUTY(nr));
}
for (nr = 0; nr < 3; nr++) {
data->temp[nr] = f71805f_read8(data,
@ -333,35 +396,43 @@ static ssize_t show_in0(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%ld\n", in0_from_reg(data->in[0]));
return sprintf(buf, "%ld\n", in0_from_reg(data->in[nr]));
}
static ssize_t show_in0_max(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[0]));
return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[nr]));
}
static ssize_t show_in0_min(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[0]));
return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[nr]));
}
static ssize_t set_in0_max(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
long val = simple_strtol(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->in_high[0] = in0_to_reg(val);
f71805f_write8(data, F71805F_REG_IN_HIGH(0), data->in_high[0]);
data->in_high[nr] = in0_to_reg(val);
f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]);
mutex_unlock(&data->update_lock);
return count;
@ -371,11 +442,13 @@ static ssize_t set_in0_min(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
long val = simple_strtol(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->in_low[0] = in0_to_reg(val);
f71805f_write8(data, F71805F_REG_IN_LOW(0), data->in_low[0]);
data->in_low[nr] = in0_to_reg(val);
f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]);
mutex_unlock(&data->update_lock);
return count;
@ -463,6 +536,16 @@ static ssize_t show_fan_min(struct device *dev, struct device_attribute
return sprintf(buf, "%ld\n", fan_from_reg(data->fan_low[nr]));
}
static ssize_t show_fan_target(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%ld\n", fan_from_reg(data->fan_target[nr]));
}
static ssize_t set_fan_min(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
@ -479,6 +562,157 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute
return count;
}
static ssize_t set_fan_target(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
long val = simple_strtol(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->fan_target[nr] = fan_to_reg(val);
f71805f_write16(data, F71805F_REG_FAN_TARGET(nr),
data->fan_target[nr]);
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%d\n", (int)data->pwm[nr]);
}
static ssize_t show_pwm_enable(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
int mode;
switch (data->fan_ctrl[nr] & FAN_CTRL_MODE_MASK) {
case FAN_CTRL_MODE_SPEED:
mode = 3;
break;
case FAN_CTRL_MODE_TEMPERATURE:
mode = 2;
break;
default: /* MANUAL */
mode = 1;
}
return sprintf(buf, "%d\n", mode);
}
static ssize_t show_pwm_freq(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%lu\n", pwm_freq_from_reg(data->pwm_freq[nr]));
}
static ssize_t show_pwm_mode(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%d\n", pwm_mode_from_reg(data->fan_ctrl[nr]));
}
static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
unsigned long val = simple_strtoul(buf, NULL, 10);
if (val > 255)
return -EINVAL;
mutex_lock(&data->update_lock);
data->pwm[nr] = val;
f71805f_write8(data, F71805F_REG_PWM_DUTY(nr), data->pwm[nr]);
mutex_unlock(&data->update_lock);
return count;
}
static struct attribute *f71805f_attr_pwm[];
static ssize_t set_pwm_enable(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
unsigned long val = simple_strtoul(buf, NULL, 10);
u8 reg;
if (val < 1 || val > 3)
return -EINVAL;
if (val > 1) { /* Automatic mode, user can't set PWM value */
if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr],
S_IRUGO))
dev_dbg(dev, "chmod -w pwm%d failed\n", nr + 1);
}
mutex_lock(&data->update_lock);
reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(nr))
& ~FAN_CTRL_MODE_MASK;
switch (val) {
case 1:
reg |= FAN_CTRL_MODE_MANUAL;
break;
case 2:
reg |= FAN_CTRL_MODE_TEMPERATURE;
break;
case 3:
reg |= FAN_CTRL_MODE_SPEED;
break;
}
data->fan_ctrl[nr] = reg;
f71805f_write8(data, F71805F_REG_FAN_CTRL(nr), reg);
mutex_unlock(&data->update_lock);
if (val == 1) { /* Manual mode, user can set PWM value */
if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr],
S_IRUGO | S_IWUSR))
dev_dbg(dev, "chmod +w pwm%d failed\n", nr + 1);
}
return count;
}
static ssize_t set_pwm_freq(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
unsigned long val = simple_strtoul(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->pwm_freq[nr] = pwm_freq_to_reg(val);
f71805f_write8(data, F71805F_REG_PWM_FREQ(nr), data->pwm_freq[nr]);
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
char *buf)
{
@ -557,7 +791,7 @@ static ssize_t show_alarms_in(struct device *dev, struct device_attribute
{
struct f71805f_data *data = f71805f_update_device(dev);
return sprintf(buf, "%lu\n", data->alarms & 0x1ff);
return sprintf(buf, "%lu\n", data->alarms & 0x7ff);
}
static ssize_t show_alarms_fan(struct device *dev, struct device_attribute
@ -594,9 +828,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute
return sprintf(buf, "%s\n", data->name);
}
static DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL);
static DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR, show_in0_max, set_in0_max);
static DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR, show_in0_min, set_in0_min);
static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0);
static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR,
show_in0_max, set_in0_max, 0);
static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR,
show_in0_min, set_in0_min, 0);
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1);
static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR,
show_in_max, set_in_max, 1);
@ -637,16 +873,32 @@ static SENSOR_DEVICE_ATTR(in8_max, S_IRUGO | S_IWUSR,
show_in_max, set_in_max, 8);
static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR,
show_in_min, set_in_min, 8);
static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_in0, NULL, 9);
static SENSOR_DEVICE_ATTR(in9_max, S_IRUGO | S_IWUSR,
show_in0_max, set_in0_max, 9);
static SENSOR_DEVICE_ATTR(in9_min, S_IRUGO | S_IWUSR,
show_in0_min, set_in0_min, 9);
static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_in0, NULL, 10);
static SENSOR_DEVICE_ATTR(in10_max, S_IRUGO | S_IWUSR,
show_in0_max, set_in0_max, 10);
static SENSOR_DEVICE_ATTR(in10_min, S_IRUGO | S_IWUSR,
show_in0_min, set_in0_min, 10);
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
show_fan_min, set_fan_min, 0);
static SENSOR_DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR,
show_fan_target, set_fan_target, 0);
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR,
show_fan_min, set_fan_min, 1);
static SENSOR_DEVICE_ATTR(fan2_target, S_IRUGO | S_IWUSR,
show_fan_target, set_fan_target, 1);
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2);
static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO | S_IWUSR,
show_fan_min, set_fan_min, 2);
static SENSOR_DEVICE_ATTR(fan3_target, S_IRUGO | S_IWUSR,
show_fan_target, set_fan_target, 2);
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR,
@ -667,6 +919,27 @@ static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR,
show_temp_hyst, set_temp_hyst, 2);
static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2);
/* pwm (value) files are created read-only, write permission is
then added or removed dynamically as needed */
static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO, show_pwm, set_pwm, 0);
static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
show_pwm_enable, set_pwm_enable, 0);
static SENSOR_DEVICE_ATTR(pwm1_freq, S_IRUGO | S_IWUSR,
show_pwm_freq, set_pwm_freq, 0);
static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0);
static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO, show_pwm, set_pwm, 1);
static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR,
show_pwm_enable, set_pwm_enable, 1);
static SENSOR_DEVICE_ATTR(pwm2_freq, S_IRUGO | S_IWUSR,
show_pwm_freq, set_pwm_freq, 1);
static SENSOR_DEVICE_ATTR(pwm2_mode, S_IRUGO, show_pwm_mode, NULL, 1);
static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO, show_pwm, set_pwm, 2);
static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR,
show_pwm_enable, set_pwm_enable, 2);
static SENSOR_DEVICE_ATTR(pwm3_freq, S_IRUGO | S_IWUSR,
show_pwm_freq, set_pwm_freq, 2);
static SENSOR_DEVICE_ATTR(pwm3_mode, S_IRUGO, show_pwm_mode, NULL, 2);
static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0);
static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1);
static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2);
@ -676,6 +949,8 @@ static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5);
static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6);
static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7);
static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8);
static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 9);
static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 10);
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 11);
static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 12);
static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13);
@ -689,9 +964,9 @@ static DEVICE_ATTR(alarms_temp, S_IRUGO, show_alarms_temp, NULL);
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static struct attribute *f71805f_attributes[] = {
&dev_attr_in0_input.attr,
&dev_attr_in0_max.attr,
&dev_attr_in0_min.attr,
&sensor_dev_attr_in0_input.dev_attr.attr,
&sensor_dev_attr_in0_max.dev_attr.attr,
&sensor_dev_attr_in0_min.dev_attr.attr,
&sensor_dev_attr_in1_input.dev_attr.attr,
&sensor_dev_attr_in1_max.dev_attr.attr,
&sensor_dev_attr_in1_min.dev_attr.attr,
@ -701,9 +976,6 @@ static struct attribute *f71805f_attributes[] = {
&sensor_dev_attr_in3_input.dev_attr.attr,
&sensor_dev_attr_in3_max.dev_attr.attr,
&sensor_dev_attr_in3_min.dev_attr.attr,
&sensor_dev_attr_in4_input.dev_attr.attr,
&sensor_dev_attr_in4_max.dev_attr.attr,
&sensor_dev_attr_in4_min.dev_attr.attr,
&sensor_dev_attr_in5_input.dev_attr.attr,
&sensor_dev_attr_in5_max.dev_attr.attr,
&sensor_dev_attr_in5_min.dev_attr.attr,
@ -713,9 +985,29 @@ static struct attribute *f71805f_attributes[] = {
&sensor_dev_attr_in7_input.dev_attr.attr,
&sensor_dev_attr_in7_max.dev_attr.attr,
&sensor_dev_attr_in7_min.dev_attr.attr,
&sensor_dev_attr_in8_input.dev_attr.attr,
&sensor_dev_attr_in8_max.dev_attr.attr,
&sensor_dev_attr_in8_min.dev_attr.attr,
&sensor_dev_attr_fan1_input.dev_attr.attr,
&sensor_dev_attr_fan1_min.dev_attr.attr,
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
&sensor_dev_attr_fan1_target.dev_attr.attr,
&sensor_dev_attr_fan2_input.dev_attr.attr,
&sensor_dev_attr_fan2_min.dev_attr.attr,
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
&sensor_dev_attr_fan2_target.dev_attr.attr,
&sensor_dev_attr_fan3_input.dev_attr.attr,
&sensor_dev_attr_fan3_min.dev_attr.attr,
&sensor_dev_attr_fan3_alarm.dev_attr.attr,
&sensor_dev_attr_fan3_target.dev_attr.attr,
&sensor_dev_attr_pwm1.dev_attr.attr,
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
&sensor_dev_attr_pwm1_mode.dev_attr.attr,
&sensor_dev_attr_pwm2.dev_attr.attr,
&sensor_dev_attr_pwm2_enable.dev_attr.attr,
&sensor_dev_attr_pwm2_mode.dev_attr.attr,
&sensor_dev_attr_pwm3.dev_attr.attr,
&sensor_dev_attr_pwm3_enable.dev_attr.attr,
&sensor_dev_attr_pwm3_mode.dev_attr.attr,
&sensor_dev_attr_temp1_input.dev_attr.attr,
&sensor_dev_attr_temp1_max.dev_attr.attr,
@ -734,11 +1026,9 @@ static struct attribute *f71805f_attributes[] = {
&sensor_dev_attr_in1_alarm.dev_attr.attr,
&sensor_dev_attr_in2_alarm.dev_attr.attr,
&sensor_dev_attr_in3_alarm.dev_attr.attr,
&sensor_dev_attr_in4_alarm.dev_attr.attr,
&sensor_dev_attr_in5_alarm.dev_attr.attr,
&sensor_dev_attr_in6_alarm.dev_attr.attr,
&sensor_dev_attr_in7_alarm.dev_attr.attr,
&sensor_dev_attr_in8_alarm.dev_attr.attr,
&dev_attr_alarms_in.attr,
&sensor_dev_attr_temp1_alarm.dev_attr.attr,
&sensor_dev_attr_temp2_alarm.dev_attr.attr,
@ -754,29 +1044,59 @@ static const struct attribute_group f71805f_group = {
.attrs = f71805f_attributes,
};
static struct attribute *f71805f_attributes_fan[3][4] = {
static struct attribute *f71805f_attributes_optin[4][5] = {
{
&sensor_dev_attr_fan1_input.dev_attr.attr,
&sensor_dev_attr_fan1_min.dev_attr.attr,
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
&sensor_dev_attr_in4_input.dev_attr.attr,
&sensor_dev_attr_in4_max.dev_attr.attr,
&sensor_dev_attr_in4_min.dev_attr.attr,
&sensor_dev_attr_in4_alarm.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan2_input.dev_attr.attr,
&sensor_dev_attr_fan2_min.dev_attr.attr,
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
&sensor_dev_attr_in8_input.dev_attr.attr,
&sensor_dev_attr_in8_max.dev_attr.attr,
&sensor_dev_attr_in8_min.dev_attr.attr,
&sensor_dev_attr_in8_alarm.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan3_input.dev_attr.attr,
&sensor_dev_attr_fan3_min.dev_attr.attr,
&sensor_dev_attr_fan3_alarm.dev_attr.attr,
&sensor_dev_attr_in9_input.dev_attr.attr,
&sensor_dev_attr_in9_max.dev_attr.attr,
&sensor_dev_attr_in9_min.dev_attr.attr,
&sensor_dev_attr_in9_alarm.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_in10_input.dev_attr.attr,
&sensor_dev_attr_in10_max.dev_attr.attr,
&sensor_dev_attr_in10_min.dev_attr.attr,
&sensor_dev_attr_in10_alarm.dev_attr.attr,
NULL
}
};
static const struct attribute_group f71805f_group_fan[3] = {
{ .attrs = f71805f_attributes_fan[0] },
{ .attrs = f71805f_attributes_fan[1] },
{ .attrs = f71805f_attributes_fan[2] },
static const struct attribute_group f71805f_group_optin[4] = {
{ .attrs = f71805f_attributes_optin[0] },
{ .attrs = f71805f_attributes_optin[1] },
{ .attrs = f71805f_attributes_optin[2] },
{ .attrs = f71805f_attributes_optin[3] },
};
/* We don't include pwm_freq files in the arrays above, because they must be
created conditionally (only if pwm_mode is 1 == PWM) */
static struct attribute *f71805f_attributes_pwm_freq[] = {
&sensor_dev_attr_pwm1_freq.dev_attr.attr,
&sensor_dev_attr_pwm2_freq.dev_attr.attr,
&sensor_dev_attr_pwm3_freq.dev_attr.attr,
NULL
};
static const struct attribute_group f71805f_group_pwm_freq = {
.attrs = f71805f_attributes_pwm_freq,
};
/* We also need an indexed access to pwmN files to toggle writability */
static struct attribute *f71805f_attr_pwm[] = {
&sensor_dev_attr_pwm1.dev_attr.attr,
&sensor_dev_attr_pwm2.dev_attr.attr,
&sensor_dev_attr_pwm3.dev_attr.attr,
};
/*
@ -798,18 +1118,30 @@ static void __devinit f71805f_init_device(struct f71805f_data *data)
/* Fan monitoring can be disabled. If it is, we won't be polling
the register values, and won't create the related sysfs files. */
for (i = 0; i < 3; i++) {
reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(i));
if (!(reg & 0x80))
data->fan_enabled |= (1 << i);
data->fan_ctrl[i] = f71805f_read8(data,
F71805F_REG_FAN_CTRL(i));
/* Clear latch full bit, else "speed mode" fan speed control
doesn't work */
if (data->fan_ctrl[i] & FAN_CTRL_LATCH_FULL) {
data->fan_ctrl[i] &= ~FAN_CTRL_LATCH_FULL;
f71805f_write8(data, F71805F_REG_FAN_CTRL(i),
data->fan_ctrl[i]);
}
}
}
static int __devinit f71805f_probe(struct platform_device *pdev)
{
struct f71805f_sio_data *sio_data = pdev->dev.platform_data;
struct f71805f_data *data;
struct resource *res;
int i, err;
static const char *names[] = {
"f71805f",
"f71872f",
};
if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) {
err = -ENOMEM;
printk(KERN_ERR DRVNAME ": Out of memory\n");
@ -819,24 +1151,69 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
data->addr = res->start;
mutex_init(&data->lock);
data->name = "f71805f";
data->name = names[sio_data->kind];
mutex_init(&data->update_lock);
platform_set_drvdata(pdev, data);
/* Some voltage inputs depend on chip model and configuration */
switch (sio_data->kind) {
case f71805f:
data->has_in = 0x1ff;
break;
case f71872f:
data->has_in = 0x6ef;
if (sio_data->fnsel1 & 0x01)
data->has_in |= (1 << 4); /* in4 */
if (sio_data->fnsel1 & 0x02)
data->has_in |= (1 << 8); /* in8 */
break;
}
/* Initialize the F71805F chip */
f71805f_init_device(data);
/* Register sysfs interface files */
if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group)))
goto exit_free;
for (i = 0; i < 3; i++) {
if (!(data->fan_enabled & (1 << i)))
continue;
if (data->has_in & (1 << 4)) { /* in4 */
if ((err = sysfs_create_group(&pdev->dev.kobj,
&f71805f_group_fan[i])))
&f71805f_group_optin[0])))
goto exit_remove_files;
}
if (data->has_in & (1 << 8)) { /* in8 */
if ((err = sysfs_create_group(&pdev->dev.kobj,
&f71805f_group_optin[1])))
goto exit_remove_files;
}
if (data->has_in & (1 << 9)) { /* in9 (F71872F/FG only) */
if ((err = sysfs_create_group(&pdev->dev.kobj,
&f71805f_group_optin[2])))
goto exit_remove_files;
}
if (data->has_in & (1 << 10)) { /* in9 (F71872F/FG only) */
if ((err = sysfs_create_group(&pdev->dev.kobj,
&f71805f_group_optin[3])))
goto exit_remove_files;
}
for (i = 0; i < 3; i++) {
/* If control mode is PWM, create pwm_freq file */
if (!(data->fan_ctrl[i] & FAN_CTRL_DC_MODE)) {
if ((err = sysfs_create_file(&pdev->dev.kobj,
f71805f_attributes_pwm_freq[i])))
goto exit_remove_files;
}
/* If PWM is in manual mode, add write permission */
if (data->fan_ctrl[i] & FAN_CTRL_MODE_MANUAL) {
if ((err = sysfs_chmod_file(&pdev->dev.kobj,
f71805f_attr_pwm[i],
S_IRUGO | S_IWUSR))) {
dev_err(&pdev->dev, "chmod +w pwm%d failed\n",
i + 1);
goto exit_remove_files;
}
}
}
data->class_dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(data->class_dev)) {
@ -849,8 +1226,9 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
exit_remove_files:
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
for (i = 0; i < 3; i++)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]);
for (i = 0; i < 4; i++)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
exit_free:
platform_set_drvdata(pdev, NULL);
kfree(data);
@ -866,8 +1244,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev)
platform_set_drvdata(pdev, NULL);
hwmon_device_unregister(data->class_dev);
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
for (i = 0; i < 3; i++)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]);
for (i = 0; i < 4; i++)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
kfree(data);
return 0;
@ -882,7 +1261,8 @@ static struct platform_driver f71805f_driver = {
.remove = __devexit_p(f71805f_remove),
};
static int __init f71805f_device_add(unsigned short address)
static int __init f71805f_device_add(unsigned short address,
const struct f71805f_sio_data *sio_data)
{
struct resource res = {
.start = address,
@ -906,26 +1286,45 @@ static int __init f71805f_device_add(unsigned short address)
goto exit_device_put;
}
pdev->dev.platform_data = kmalloc(sizeof(struct f71805f_sio_data),
GFP_KERNEL);
if (!pdev->dev.platform_data) {
err = -ENOMEM;
printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
goto exit_device_put;
}
memcpy(pdev->dev.platform_data, sio_data,
sizeof(struct f71805f_sio_data));
err = platform_device_add(pdev);
if (err) {
printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
err);
goto exit_device_put;
goto exit_kfree_data;
}
return 0;
exit_kfree_data:
kfree(pdev->dev.platform_data);
pdev->dev.platform_data = NULL;
exit_device_put:
platform_device_put(pdev);
exit:
return err;
}
static int __init f71805f_find(int sioaddr, unsigned short *address)
static int __init f71805f_find(int sioaddr, unsigned short *address,
struct f71805f_sio_data *sio_data)
{
int err = -ENODEV;
u16 devid;
static const char *names[] = {
"F71805F/FG",
"F71872F/FG",
};
superio_enter(sioaddr);
devid = superio_inw(sioaddr, SIO_REG_MANID);
@ -933,7 +1332,15 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
goto exit;
devid = superio_inw(sioaddr, SIO_REG_DEVID);
if (devid != SIO_F71805F_ID) {
switch (devid) {
case SIO_F71805F_ID:
sio_data->kind = f71805f;
break;
case SIO_F71872F_ID:
sio_data->kind = f71872f;
sio_data->fnsel1 = superio_inb(sioaddr, SIO_REG_FNSEL1);
break;
default:
printk(KERN_INFO DRVNAME ": Unsupported Fintek device, "
"skipping\n");
goto exit;
@ -952,10 +1359,12 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
"skipping\n");
goto exit;
}
*address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */
err = 0;
printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n",
*address, superio_inb(sioaddr, SIO_REG_DEVREV));
printk(KERN_INFO DRVNAME ": Found %s chip at %#x, revision %u\n",
names[sio_data->kind], *address,
superio_inb(sioaddr, SIO_REG_DEVREV));
exit:
superio_exit(sioaddr);
@ -966,9 +1375,10 @@ static int __init f71805f_init(void)
{
int err;
unsigned short address;
struct f71805f_sio_data sio_data;
if (f71805f_find(0x2e, &address)
&& f71805f_find(0x4e, &address))
if (f71805f_find(0x2e, &address, &sio_data)
&& f71805f_find(0x4e, &address, &sio_data))
return -ENODEV;
err = platform_driver_register(&f71805f_driver);
@ -976,7 +1386,7 @@ static int __init f71805f_init(void)
goto exit;
/* Sets global pdev as a side effect */
err = f71805f_device_add(address);
err = f71805f_device_add(address, &sio_data);
if (err)
goto exit_driver;
@ -990,13 +1400,16 @@ exit:
static void __exit f71805f_exit(void)
{
kfree(pdev->dev.platform_data);
pdev->dev.platform_data = NULL;
platform_device_unregister(pdev);
platform_driver_unregister(&f71805f_driver);
}
MODULE_AUTHOR("Jean Delvare <khali@linux-fr>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("F71805F hardware monitoring driver");
MODULE_DESCRIPTION("F71805F/F71872F hardware monitoring driver");
module_init(f71805f_init);
module_exit(f71805f_exit);

View File

@ -478,74 +478,64 @@ static struct attribute_group hdaps_attribute_group = {
/* Module stuff */
/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
static int hdaps_dmi_match(struct dmi_system_id *id)
static int __init hdaps_dmi_match(struct dmi_system_id *id)
{
printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
return 1;
}
/* hdaps_dmi_match_invert - found an inverted match. */
static int hdaps_dmi_match_invert(struct dmi_system_id *id)
static int __init hdaps_dmi_match_invert(struct dmi_system_id *id)
{
hdaps_invert = 1;
printk(KERN_INFO "hdaps: inverting axis readings.\n");
return hdaps_dmi_match(id);
}
#define HDAPS_DMI_MATCH_NORMAL(model) { \
.ident = "IBM " model, \
#define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \
.ident = vendor " " model, \
.callback = hdaps_dmi_match, \
.matches = { \
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
} \
}
#define HDAPS_DMI_MATCH_INVERT(model) { \
.ident = "IBM " model, \
#define HDAPS_DMI_MATCH_INVERT(vendor, model) { \
.ident = vendor " " model, \
.callback = hdaps_dmi_match_invert, \
.matches = { \
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
} \
}
#define HDAPS_DMI_MATCH_LENOVO(model) { \
.ident = "Lenovo " model, \
.callback = hdaps_dmi_match_invert, \
.matches = { \
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), \
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
} \
}
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
"ThinkPad T42p", so the order of the entries matters.
If your ThinkPad is not recognized, please update to latest
BIOS. This is especially the case for some R52 ThinkPads. */
static struct dmi_system_id __initdata hdaps_whitelist[] = {
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
{ .ident = NULL }
};
static int __init hdaps_init(void)
{
int ret;
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
"ThinkPad T42p", so the order of the entries matters */
struct dmi_system_id hdaps_whitelist[] = {
HDAPS_DMI_MATCH_NORMAL("ThinkPad H"),
HDAPS_DMI_MATCH_INVERT("ThinkPad R50p"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad R50"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad R51"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad R52"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad H"), /* R52 (1846AQG) */
HDAPS_DMI_MATCH_INVERT("ThinkPad T41p"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad T41"),
HDAPS_DMI_MATCH_INVERT("ThinkPad T42p"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad T42"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad T43"),
HDAPS_DMI_MATCH_LENOVO("ThinkPad T60p"),
HDAPS_DMI_MATCH_LENOVO("ThinkPad T60"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad X40"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad X41"),
HDAPS_DMI_MATCH_LENOVO("ThinkPad X60"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad Z60m"),
{ .ident = NULL }
};
if (!dmi_check_system(hdaps_whitelist)) {
printk(KERN_WARNING "hdaps: supported laptop not found!\n");
ret = -ENODEV;

View File

@ -1,7 +1,7 @@
/*
hwmon-vid.c - VID/VRM/VRD voltage conversions
Copyright (c) 2004 Rudolf Marek <r.marek@sh.cvut.cz>
Copyright (c) 2004 Rudolf Marek <r.marek@assembler.cz>
Partly imported from i2c-vid.h of the lm_sensors project
Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
@ -232,7 +232,7 @@ u8 vid_which_vrm(void)
EXPORT_SYMBOL(vid_from_reg);
EXPORT_SYMBOL(vid_which_vrm);
MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>");
MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
MODULE_DESCRIPTION("hwmon-vid driver");
MODULE_LICENSE("GPL");

View File

@ -3,7 +3,7 @@
monitoring.
Supports: IT8705F Super I/O chip w/LPC interface
IT8712F Super I/O chip w/LPC interface & SMBus
IT8712F Super I/O chip w/LPC interface
IT8716F Super I/O chip w/LPC interface
IT8718F Super I/O chip w/LPC interface
Sis950 A clone of the IT8705F
@ -41,12 +41,8 @@
#include <asm/io.h>
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END };
static unsigned short isa_address;
/* Insmod parameters */
I2C_CLIENT_INSMOD_4(it87, it8712, it8716, it8718);
enum chips { it87, it8712, it8716, it8718 };
#define REG 0x2e /* The register to read/write */
#define DEV 0x07 /* Register: Logical device select */
@ -162,8 +158,6 @@ static u8 vid_value;
#define IT87_REG_TEMP_HIGH(nr) (0x40 + (nr) * 2)
#define IT87_REG_TEMP_LOW(nr) (0x41 + (nr) * 2)
#define IT87_REG_I2C_ADDR 0x48
#define IT87_REG_VIN_ENABLE 0x50
#define IT87_REG_TEMP_ENABLE 0x51
@ -242,33 +236,22 @@ struct it87_data {
};
static int it87_attach_adapter(struct i2c_adapter *adapter);
static int it87_isa_attach_adapter(struct i2c_adapter *adapter);
static int it87_detect(struct i2c_adapter *adapter, int address, int kind);
static int it87_detect(struct i2c_adapter *adapter);
static int it87_detach_client(struct i2c_client *client);
static int it87_read_value(struct i2c_client *client, u8 reg);
static int it87_write_value(struct i2c_client *client, u8 reg, u8 value);
static void it87_write_value(struct i2c_client *client, u8 reg, u8 value);
static struct it87_data *it87_update_device(struct device *dev);
static int it87_check_pwm(struct i2c_client *client);
static void it87_init_client(struct i2c_client *client, struct it87_data *data);
static struct i2c_driver it87_driver = {
.driver = {
.name = "it87",
},
.id = I2C_DRIVERID_IT87,
.attach_adapter = it87_attach_adapter,
.detach_client = it87_detach_client,
};
static struct i2c_driver it87_isa_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "it87-isa",
},
.attach_adapter = it87_isa_attach_adapter,
.attach_adapter = it87_detect,
.detach_client = it87_detach_client,
};
@ -850,22 +833,6 @@ static const struct attribute_group it87_group_opt = {
.attrs = it87_attributes_opt,
};
/* This function is called when:
* it87_driver is inserted (when this module is loaded), for each
available adapter
* when a new adapter is inserted (and it87_driver is still present) */
static int it87_attach_adapter(struct i2c_adapter *adapter)
{
if (!(adapter->class & I2C_CLASS_HWMON))
return 0;
return i2c_probe(adapter, &addr_data, it87_detect);
}
static int it87_isa_attach_adapter(struct i2c_adapter *adapter)
{
return it87_detect(adapter, isa_address, -1);
}
/* SuperIO detection - will change isa_address if a chip is found */
static int __init it87_find(unsigned short *address)
{
@ -916,29 +883,20 @@ exit:
}
/* This function is called by i2c_probe */
static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
static int it87_detect(struct i2c_adapter *adapter)
{
int i;
struct i2c_client *new_client;
struct it87_data *data;
int err = 0;
const char *name = "";
int is_isa = i2c_is_isa_adapter(adapter);
const char *name;
int enable_pwm_interface;
if (!is_isa &&
!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* Reserve the ISA region */
if (is_isa)
if (!request_region(address, IT87_EXTENT,
it87_isa_driver.driver.name))
goto ERROR0;
/* For now, we presume we have a valid client. We create the
client structure, even though we cannot fill it completely yet.
But it allows us to access it87_{read,write}_value. */
if (!request_region(isa_address, IT87_EXTENT,
it87_isa_driver.driver.name)){
err = -EBUSY;
goto ERROR0;
}
if (!(data = kzalloc(sizeof(struct it87_data), GFP_KERNEL))) {
err = -ENOMEM;
@ -946,80 +904,46 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
}
new_client = &data->client;
if (is_isa)
mutex_init(&data->lock);
mutex_init(&data->lock);
i2c_set_clientdata(new_client, data);
new_client->addr = address;
new_client->addr = isa_address;
new_client->adapter = adapter;
new_client->driver = is_isa ? &it87_isa_driver : &it87_driver;
new_client->flags = 0;
new_client->driver = &it87_isa_driver;
/* Now, we do the remaining detection. */
if (kind < 0) {
if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80)
|| (!is_isa
&& it87_read_value(new_client, IT87_REG_I2C_ADDR) != address)) {
err = -ENODEV;
goto ERROR2;
}
if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80)
|| it87_read_value(new_client, IT87_REG_CHIPID) != 0x90) {
err = -ENODEV;
goto ERROR2;
}
/* Determine the chip type. */
if (kind <= 0) {
i = it87_read_value(new_client, IT87_REG_CHIPID);
if (i == 0x90) {
kind = it87;
if (is_isa) {
switch (chip_type) {
case IT8712F_DEVID:
kind = it8712;
break;
case IT8716F_DEVID:
kind = it8716;
break;
case IT8718F_DEVID:
kind = it8718;
break;
}
}
}
else {
if (kind == 0)
dev_info(&adapter->dev,
"Ignoring 'force' parameter for unknown chip at "
"adapter %d, address 0x%02x\n",
i2c_adapter_id(adapter), address);
err = -ENODEV;
goto ERROR2;
}
}
if (kind == it87) {
name = "it87";
} else if (kind == it8712) {
switch (chip_type) {
case IT8712F_DEVID:
data->type = it8712;
name = "it8712";
} else if (kind == it8716) {
break;
case IT8716F_DEVID:
data->type = it8716;
name = "it8716";
} else if (kind == it8718) {
break;
case IT8718F_DEVID:
data->type = it8718;
name = "it8718";
break;
default:
data->type = it87;
name = "it87";
}
/* Fill in the remaining client fields and put it into the global list */
strlcpy(new_client->name, name, I2C_NAME_SIZE);
data->type = kind;
data->valid = 0;
mutex_init(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR2;
if (!is_isa)
dev_info(&new_client->dev, "The I2C interface to IT87xxF "
"hardware monitoring chips is deprecated. Please "
"report if you still rely on it.\n");
/* Check PWM configuration */
enable_pwm_interface = it87_check_pwm(new_client);
@ -1129,8 +1053,7 @@ ERROR3:
ERROR2:
kfree(data);
ERROR1:
if (is_isa)
release_region(address, IT87_EXTENT);
release_region(isa_address, IT87_EXTENT);
ERROR0:
return err;
}
@ -1147,50 +1070,39 @@ static int it87_detach_client(struct i2c_client *client)
if ((err = i2c_detach_client(client)))
return err;
if(i2c_is_isa_client(client))
release_region(client->addr, IT87_EXTENT);
release_region(client->addr, IT87_EXTENT);
kfree(data);
return 0;
}
/* The SMBus locks itself, but ISA access must be locked explicitly!
We don't want to lock the whole ISA bus, so we lock each client
separately.
/* ISA access must be locked explicitly!
We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
would slow down the IT87 access and should not be necessary. */
static int it87_read_value(struct i2c_client *client, u8 reg)
{
struct it87_data *data = i2c_get_clientdata(client);
int res;
if (i2c_is_isa_client(client)) {
mutex_lock(&data->lock);
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
res = inb_p(client->addr + IT87_DATA_REG_OFFSET);
mutex_unlock(&data->lock);
return res;
} else
return i2c_smbus_read_byte_data(client, reg);
mutex_lock(&data->lock);
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
res = inb_p(client->addr + IT87_DATA_REG_OFFSET);
mutex_unlock(&data->lock);
return res;
}
/* The SMBus locks itself, but ISA access muse be locked explicitly!
We don't want to lock the whole ISA bus, so we lock each client
separately.
/* ISA access must be locked explicitly!
We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
would slow down the IT87 access and should not be necessary. */
static int it87_write_value(struct i2c_client *client, u8 reg, u8 value)
static void it87_write_value(struct i2c_client *client, u8 reg, u8 value)
{
struct it87_data *data = i2c_get_clientdata(client);
if (i2c_is_isa_client(client)) {
mutex_lock(&data->lock);
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
outb_p(value, client->addr + IT87_DATA_REG_OFFSET);
mutex_unlock(&data->lock);
return 0;
} else
return i2c_smbus_write_byte_data(client, reg, value);
mutex_lock(&data->lock);
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
outb_p(value, client->addr + IT87_DATA_REG_OFFSET);
mutex_unlock(&data->lock);
}
/* Return 1 if and only if the PWM interface is safe to use */
@ -1426,26 +1338,14 @@ static int __init sm_it87_init(void)
{
int res;
res = i2c_add_driver(&it87_driver);
if (res)
if ((res = it87_find(&isa_address)))
return res;
if (!it87_find(&isa_address)) {
res = i2c_isa_add_driver(&it87_isa_driver);
if (res) {
i2c_del_driver(&it87_driver);
return res;
}
}
return 0;
return i2c_isa_add_driver(&it87_isa_driver);
}
static void __exit sm_it87_exit(void)
{
if (isa_address)
i2c_isa_del_driver(&it87_isa_driver);
i2c_del_driver(&it87_driver);
i2c_isa_del_driver(&it87_isa_driver);
}

View File

@ -1,7 +1,7 @@
/*
* k8temp.c - Linux kernel module for hardware monitoring
*
* Copyright (C) 2006 Rudolf Marek <r.marek@sh.cvut.cz>
* Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz>
*
* Inspired from the w83785 and amd756 drivers.
*
@ -286,7 +286,7 @@ static void __exit k8temp_exit(void)
pci_unregister_driver(&k8temp_driver);
}
MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>");
MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
MODULE_DESCRIPTION("AMD K8 core temperature monitor");
MODULE_LICENSE("GPL");

View File

@ -1000,7 +1000,7 @@ static int pc87360_detect(struct i2c_adapter *adapter)
(i&0x02) ? "external" : "internal");
data->vid_conf = confreg[3];
data->vrm = 90;
data->vrm = vid_which_vrm();
}
/* Fan clock dividers may be needed before any data is read */

View 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);

View File

@ -3,7 +3,7 @@
the Winbond W83627EHF Super-I/O chip
Copyright (C) 2005 Jean Delvare <khali@linux-fr.org>
Copyright (C) 2006 Yuan Mu (Winbond),
Rudolf Marek <r.marek@sh.cvut.cz>
Rudolf Marek <r.marek@assembler.cz>
David Hubbard <david.c.hubbard@gmail.com>
Shamelessly ripped from the w83627hf driver

View File

@ -3,7 +3,7 @@
monitoring
Copyright (C) 2004, 2005 Winbond Electronics Corp.
Chunhao Huang <DZShen@Winbond.com.tw>,
Rudolf Marek <r.marek@sh.cvut.cz>
Rudolf Marek <r.marek@assembler.cz>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* i2c-ali1563.c - i2c driver for the ALi 1563 Southbridge
*
* Copyright (C) 2004 Patrick Mochel
* 2005 Rudolf Marek <r.marek@sh.cvut.cz>
* 2005 Rudolf Marek <r.marek@assembler.cz>
*
* The 1563 southbridge is deceptively similar to the 1533, with a
* few notable exceptions. One of those happens to be the fact they

View File

@ -142,7 +142,6 @@
#define I2C_DRIVERID_MTP008 1023
#define I2C_DRIVERID_DS1621 1024
#define I2C_DRIVERID_ADM1024 1025
#define I2C_DRIVERID_IT87 1026
#define I2C_DRIVERID_CH700X 1027 /* single driver for CH7003-7009 digital pc to tv encoders */
#define I2C_DRIVERID_FSCPOS 1028
#define I2C_DRIVERID_FSCSCY 1029