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: (32 commits) Use menuconfig objects - hwmon hwmon/smsc47b397: Use dynamic sysfs callbacks hwmon/smsc47b397: Convert to a platform driver hwmon/w83781d: Deprecate W83627HF support hwmon/w83781d: Use dynamic sysfs callbacks hwmon/w83781d: Be less i2c_client-centric hwmon/w83781d: Clean up conversion macros hwmon/w83781d: No longer use i2c-isa hwmon/ams: Do not print error on systems without apple motion sensor hwmon/ams: Fix I2C read retry logic hwmon: New AD7416, AD7417 and AD7418 driver hwmon/coretemp: Add documentation hwmon: New coretemp driver i386: Use functions from library in msr driver i386: Add safe variants of rdmsr_on_cpu and wrmsr_on_cpu hwmon/lm75: Use dynamic sysfs callbacks hwmon/lm78: Use dynamic sysfs callbacks hwmon/lm78: Be less i2c_client-centric hwmon/lm78: No longer use i2c-isa hwmon: New max6650 driver ...
This commit is contained in:
commit
36f021b579
|
@ -0,0 +1,36 @@
|
|||
Kernel driver coretemp
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
* All Intel Core family
|
||||
Prefix: 'coretemp'
|
||||
CPUID: family 0x6, models 0xe, 0xf
|
||||
Datasheet: Intel 64 and IA-32 Architectures Software Developer's Manual
|
||||
Volume 3A: System Programming Guide
|
||||
|
||||
Author: Rudolf Marek
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver permits reading temperature sensor embedded inside Intel Core CPU.
|
||||
Temperature is measured in degrees Celsius and measurement resolution is
|
||||
1 degree C. Valid temperatures are from 0 to TjMax degrees C, because
|
||||
the actual value of temperature register is in fact a delta from TjMax.
|
||||
|
||||
Temperature known as TjMax is the maximum junction temperature of processor.
|
||||
Intel defines this temperature as 85C or 100C. At this temperature, protection
|
||||
mechanism will perform actions to forcibly cool down the processor. Alarm
|
||||
may be raised, if the temperature grows enough (more than TjMax) to trigger
|
||||
the Out-Of-Spec bit. Following table summarizes the exported sysfs files:
|
||||
|
||||
temp1_input - Core temperature (in millidegrees Celsius).
|
||||
temp1_crit - Maximum junction temperature (in millidegrees Celsius).
|
||||
temp1_crit_alarm - Set when Out-of-spec bit is set, never clears.
|
||||
Correct CPU operation is no longer guaranteed.
|
||||
temp1_label - Contains string "Core X", where X is processor
|
||||
number.
|
||||
|
||||
The TjMax temperature is set to 85 degrees C if undocumented model specific
|
||||
register (UMSR) 0xee has bit 30 set. If not the TjMax is 100 degrees C as
|
||||
(sometimes) documented in processor datasheet.
|
|
@ -0,0 +1,53 @@
|
|||
Kernel driver max6650
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* Maxim 6650 / 6651
|
||||
Prefix: 'max6650'
|
||||
Addresses scanned: I2C 0x1b, 0x1f, 0x48, 0x4b
|
||||
Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf
|
||||
|
||||
Authors:
|
||||
Hans J. Koch <hjk@linutronix.de>
|
||||
John Morris <john.morris@spirentcom.com>
|
||||
Claus Gindhart <claus.gindhart@kontron.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for the Maxim 6650/6651
|
||||
|
||||
The 2 devices are very similar, but the Maxim 6550 has a reduced feature
|
||||
set, e.g. only one fan-input, instead of 4 for the 6651.
|
||||
|
||||
The driver is not able to distinguish between the 2 devices.
|
||||
|
||||
The driver provides the following sensor accesses in sysfs:
|
||||
|
||||
fan1_input ro fan tachometer speed in RPM
|
||||
fan2_input ro "
|
||||
fan3_input ro "
|
||||
fan4_input ro "
|
||||
fan1_target rw desired fan speed in RPM (closed loop mode only)
|
||||
pwm1_enable rw regulator mode, 0=full on, 1=open loop, 2=closed loop
|
||||
pwm1 rw relative speed (0-255), 255=max. speed.
|
||||
Used in open loop mode only.
|
||||
fan1_div rw sets the speed range the inputs can handle. Legal
|
||||
values are 1, 2, 4, and 8. Use lower values for
|
||||
faster fans.
|
||||
|
||||
Module parameters
|
||||
-----------------
|
||||
|
||||
If your board has a BIOS that initializes the MAX6650/6651 correctly, you can
|
||||
simply load your module without parameters. It won't touch the configuration
|
||||
registers then. If your board BIOS doesn't initialize the chip, or you want
|
||||
different settings, you can set the following parameters:
|
||||
|
||||
voltage_12V: 5=5V fan, 12=12V fan, 0=don't change
|
||||
prescaler: Possible values are 1,2,4,8,16, or 0 for don't change
|
||||
clock: The clock frequency in Hz of the chip the driver should assume [254000]
|
||||
|
||||
Please have a look at the MAX6650/6651 data sheet and make sure that you fully
|
||||
understand the meaning of these parameters before you attempt to change them.
|
||||
|
|
@ -14,6 +14,10 @@ Supported chips:
|
|||
http://www.smsc.com/main/datasheets/47m14x.pdf
|
||||
http://www.smsc.com/main/tools/discontinued/47m15x.pdf
|
||||
http://www.smsc.com/main/datasheets/47m192.pdf
|
||||
* SMSC LPC47M292
|
||||
Addresses scanned: none, address read from Super I/O config space
|
||||
Prefix: 'smsc47m2'
|
||||
Datasheet: Not public
|
||||
* SMSC LPC47M997
|
||||
Addresses scanned: none, address read from Super I/O config space
|
||||
Prefix: 'smsc47m1'
|
||||
|
@ -32,9 +36,10 @@ Description
|
|||
The Standard Microsystems Corporation (SMSC) 47M1xx Super I/O chips
|
||||
contain monitoring and PWM control circuitry for two fans.
|
||||
|
||||
The 47M15x and 47M192 chips contain a full 'hardware monitoring block'
|
||||
in addition to the fan monitoring and control. The hardware monitoring
|
||||
block is not supported by the driver.
|
||||
The LPC47M15x, LPC47M192 and LPC47M292 chips contain a full 'hardware
|
||||
monitoring block' in addition to the fan monitoring and control. The
|
||||
hardware monitoring block is not supported by this driver, use the
|
||||
smsc47m192 driver for that.
|
||||
|
||||
No documentation is available for the 47M997, but it has the same device
|
||||
ID as the 47M15x and 47M192 chips and seems to be compatible.
|
||||
|
|
|
@ -2,12 +2,13 @@ Kernel driver smsc47m192
|
|||
========================
|
||||
|
||||
Supported chips:
|
||||
* SMSC LPC47M192 and LPC47M997
|
||||
* SMSC LPC47M192, LPC47M15x, LPC47M292 and LPC47M997
|
||||
Prefix: 'smsc47m192'
|
||||
Addresses scanned: I2C 0x2c - 0x2d
|
||||
Datasheet: The datasheet for LPC47M192 is publicly available from
|
||||
http://www.smsc.com/
|
||||
The LPC47M997 is compatible for hardware monitoring.
|
||||
The LPC47M15x, LPC47M292 and LPC47M997 are compatible for
|
||||
hardware monitoring.
|
||||
|
||||
Author: Hartmut Rick <linux@rick.claranet.de>
|
||||
Special thanks to Jean Delvare for careful checking
|
||||
|
@ -18,7 +19,7 @@ Description
|
|||
-----------
|
||||
|
||||
This driver implements support for the hardware sensor capabilities
|
||||
of the SMSC LPC47M192 and LPC47M997 Super-I/O chips.
|
||||
of the SMSC LPC47M192 and compatible Super-I/O chips.
|
||||
|
||||
These chips support 3 temperature channels and 8 voltage inputs
|
||||
as well as CPU voltage VID input.
|
||||
|
|
|
@ -152,6 +152,13 @@ fan[1-*]_div Fan divisor.
|
|||
Note that this is actually an internal clock divisor, which
|
||||
affects the measurable speed range, not the read value.
|
||||
|
||||
fan[1-*]_target
|
||||
Desired fan speed
|
||||
Unit: revolution/min (RPM)
|
||||
RW
|
||||
Only makes sense if the chip supports closed-loop fan speed
|
||||
control based on the measured fan speed.
|
||||
|
||||
Also see the Alarms section for status flags associated with fans.
|
||||
|
||||
|
||||
|
|
12
MAINTAINERS
12
MAINTAINERS
|
@ -1040,6 +1040,12 @@ P: Simon Arlott
|
|||
M: cxacru@fire.lp0.eu
|
||||
S: Maintained
|
||||
|
||||
CORETEMP HARDWARE MONITORING DRIVER
|
||||
P: Rudolf Marek
|
||||
M: r.marek@assembler.cz
|
||||
L: lm-sensors@lm-sensors.org
|
||||
S: Maintained
|
||||
|
||||
COSA/SRP SYNC SERIAL DRIVER
|
||||
P: Jan "Yenya" Kasprzak
|
||||
M: kas@fi.muni.cz
|
||||
|
@ -2314,6 +2320,12 @@ M: vandrove@vc.cvut.cz
|
|||
L: linux-fbdev-devel@lists.sourceforge.net (subscribers-only)
|
||||
S: Maintained
|
||||
|
||||
MAX6650 HARDWARE MONITOR AND FAN CONTROLLER DRIVER
|
||||
P: Hans J. Koch
|
||||
M: hjk@linutronix.de
|
||||
L: lm-sensors@lm-sensors.org
|
||||
S: Maintained
|
||||
|
||||
MEGARAID SCSI DRIVERS
|
||||
P: Neela Syam Kolli
|
||||
M: Neela.Kolli@engenio.com
|
||||
|
|
|
@ -45,104 +45,6 @@
|
|||
|
||||
static struct class *msr_class;
|
||||
|
||||
static inline int wrmsr_eio(u32 reg, u32 eax, u32 edx)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = wrmsr_safe(reg, eax, edx);
|
||||
if (err)
|
||||
err = -EIO;
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int rdmsr_eio(u32 reg, u32 *eax, u32 *edx)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = rdmsr_safe(reg, eax, edx);
|
||||
if (err)
|
||||
err = -EIO;
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
|
||||
struct msr_command {
|
||||
int err;
|
||||
u32 reg;
|
||||
u32 data[2];
|
||||
};
|
||||
|
||||
static void msr_smp_wrmsr(void *cmd_block)
|
||||
{
|
||||
struct msr_command *cmd = (struct msr_command *)cmd_block;
|
||||
|
||||
cmd->err = wrmsr_eio(cmd->reg, cmd->data[0], cmd->data[1]);
|
||||
}
|
||||
|
||||
static void msr_smp_rdmsr(void *cmd_block)
|
||||
{
|
||||
struct msr_command *cmd = (struct msr_command *)cmd_block;
|
||||
|
||||
cmd->err = rdmsr_eio(cmd->reg, &cmd->data[0], &cmd->data[1]);
|
||||
}
|
||||
|
||||
static inline int do_wrmsr(int cpu, u32 reg, u32 eax, u32 edx)
|
||||
{
|
||||
struct msr_command cmd;
|
||||
int ret;
|
||||
|
||||
preempt_disable();
|
||||
if (cpu == smp_processor_id()) {
|
||||
ret = wrmsr_eio(reg, eax, edx);
|
||||
} else {
|
||||
cmd.reg = reg;
|
||||
cmd.data[0] = eax;
|
||||
cmd.data[1] = edx;
|
||||
|
||||
smp_call_function_single(cpu, msr_smp_wrmsr, &cmd, 1, 1);
|
||||
ret = cmd.err;
|
||||
}
|
||||
preempt_enable();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int do_rdmsr(int cpu, u32 reg, u32 * eax, u32 * edx)
|
||||
{
|
||||
struct msr_command cmd;
|
||||
int ret;
|
||||
|
||||
preempt_disable();
|
||||
if (cpu == smp_processor_id()) {
|
||||
ret = rdmsr_eio(reg, eax, edx);
|
||||
} else {
|
||||
cmd.reg = reg;
|
||||
|
||||
smp_call_function_single(cpu, msr_smp_rdmsr, &cmd, 1, 1);
|
||||
|
||||
*eax = cmd.data[0];
|
||||
*edx = cmd.data[1];
|
||||
|
||||
ret = cmd.err;
|
||||
}
|
||||
preempt_enable();
|
||||
return ret;
|
||||
}
|
||||
|
||||
#else /* ! CONFIG_SMP */
|
||||
|
||||
static inline int do_wrmsr(int cpu, u32 reg, u32 eax, u32 edx)
|
||||
{
|
||||
return wrmsr_eio(reg, eax, edx);
|
||||
}
|
||||
|
||||
static inline int do_rdmsr(int cpu, u32 reg, u32 *eax, u32 *edx)
|
||||
{
|
||||
return rdmsr_eio(reg, eax, edx);
|
||||
}
|
||||
|
||||
#endif /* ! CONFIG_SMP */
|
||||
|
||||
static loff_t msr_seek(struct file *file, loff_t offset, int orig)
|
||||
{
|
||||
loff_t ret = -EINVAL;
|
||||
|
@ -174,9 +76,9 @@ static ssize_t msr_read(struct file *file, char __user * buf,
|
|||
return -EINVAL; /* Invalid chunk size */
|
||||
|
||||
for (; count; count -= 8) {
|
||||
err = do_rdmsr(cpu, reg, &data[0], &data[1]);
|
||||
err = rdmsr_safe_on_cpu(cpu, reg, &data[0], &data[1]);
|
||||
if (err)
|
||||
return err;
|
||||
return -EIO;
|
||||
if (copy_to_user(tmp, &data, 8))
|
||||
return -EFAULT;
|
||||
tmp += 2;
|
||||
|
@ -200,9 +102,9 @@ static ssize_t msr_write(struct file *file, const char __user *buf,
|
|||
for (; count; count -= 8) {
|
||||
if (copy_from_user(&data, tmp, 8))
|
||||
return -EFAULT;
|
||||
err = do_wrmsr(cpu, reg, data[0], data[1]);
|
||||
err = wrmsr_safe_on_cpu(cpu, reg, data[0], data[1]);
|
||||
if (err)
|
||||
return err;
|
||||
return -EIO;
|
||||
tmp += 2;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
struct msr_info {
|
||||
u32 msr_no;
|
||||
u32 l, h;
|
||||
int err;
|
||||
};
|
||||
|
||||
static void __rdmsr_on_cpu(void *info)
|
||||
|
@ -15,20 +16,38 @@ static void __rdmsr_on_cpu(void *info)
|
|||
rdmsr(rv->msr_no, rv->l, rv->h);
|
||||
}
|
||||
|
||||
void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
|
||||
static void __rdmsr_safe_on_cpu(void *info)
|
||||
{
|
||||
struct msr_info *rv = info;
|
||||
|
||||
rv->err = rdmsr_safe(rv->msr_no, &rv->l, &rv->h);
|
||||
}
|
||||
|
||||
static int _rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h, int safe)
|
||||
{
|
||||
int err = 0;
|
||||
preempt_disable();
|
||||
if (smp_processor_id() == cpu)
|
||||
rdmsr(msr_no, *l, *h);
|
||||
if (safe)
|
||||
err = rdmsr_safe(msr_no, l, h);
|
||||
else
|
||||
rdmsr(msr_no, *l, *h);
|
||||
else {
|
||||
struct msr_info rv;
|
||||
|
||||
rv.msr_no = msr_no;
|
||||
smp_call_function_single(cpu, __rdmsr_on_cpu, &rv, 0, 1);
|
||||
if (safe) {
|
||||
smp_call_function_single(cpu, __rdmsr_safe_on_cpu,
|
||||
&rv, 0, 1);
|
||||
err = rv.err;
|
||||
} else {
|
||||
smp_call_function_single(cpu, __rdmsr_on_cpu, &rv, 0, 1);
|
||||
}
|
||||
*l = rv.l;
|
||||
*h = rv.h;
|
||||
}
|
||||
preempt_enable();
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __wrmsr_on_cpu(void *info)
|
||||
|
@ -38,21 +57,63 @@ static void __wrmsr_on_cpu(void *info)
|
|||
wrmsr(rv->msr_no, rv->l, rv->h);
|
||||
}
|
||||
|
||||
void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
|
||||
static void __wrmsr_safe_on_cpu(void *info)
|
||||
{
|
||||
struct msr_info *rv = info;
|
||||
|
||||
rv->err = wrmsr_safe(rv->msr_no, rv->l, rv->h);
|
||||
}
|
||||
|
||||
static int _wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h, int safe)
|
||||
{
|
||||
int err = 0;
|
||||
preempt_disable();
|
||||
if (smp_processor_id() == cpu)
|
||||
wrmsr(msr_no, l, h);
|
||||
if (safe)
|
||||
err = wrmsr_safe(msr_no, l, h);
|
||||
else
|
||||
wrmsr(msr_no, l, h);
|
||||
else {
|
||||
struct msr_info rv;
|
||||
|
||||
rv.msr_no = msr_no;
|
||||
rv.l = l;
|
||||
rv.h = h;
|
||||
smp_call_function_single(cpu, __wrmsr_on_cpu, &rv, 0, 1);
|
||||
if (safe) {
|
||||
smp_call_function_single(cpu, __wrmsr_safe_on_cpu,
|
||||
&rv, 0, 1);
|
||||
err = rv.err;
|
||||
} else {
|
||||
smp_call_function_single(cpu, __wrmsr_on_cpu, &rv, 0, 1);
|
||||
}
|
||||
}
|
||||
preempt_enable();
|
||||
return err;
|
||||
}
|
||||
|
||||
void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
|
||||
{
|
||||
_wrmsr_on_cpu(cpu, msr_no, l, h, 0);
|
||||
}
|
||||
|
||||
void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
|
||||
{
|
||||
_rdmsr_on_cpu(cpu, msr_no, l, h, 0);
|
||||
}
|
||||
|
||||
/* These "safe" variants are slower and should be used when the target MSR
|
||||
may not actually exist. */
|
||||
int wrmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
|
||||
{
|
||||
return _wrmsr_on_cpu(cpu, msr_no, l, h, 1);
|
||||
}
|
||||
|
||||
int rdmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
|
||||
{
|
||||
return _rdmsr_on_cpu(cpu, msr_no, l, h, 1);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(rdmsr_on_cpu);
|
||||
EXPORT_SYMBOL(wrmsr_on_cpu);
|
||||
EXPORT_SYMBOL(rdmsr_safe_on_cpu);
|
||||
EXPORT_SYMBOL(wrmsr_safe_on_cpu);
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
# Hardware monitoring chip drivers configuration
|
||||
#
|
||||
|
||||
menu "Hardware Monitoring support"
|
||||
|
||||
config HWMON
|
||||
menuconfig HWMON
|
||||
tristate "Hardware Monitoring support"
|
||||
default y
|
||||
help
|
||||
|
@ -23,13 +21,15 @@ config HWMON
|
|||
This support can also be built as a module. If so, the module
|
||||
will be called hwmon.
|
||||
|
||||
if HWMON
|
||||
|
||||
config HWMON_VID
|
||||
tristate
|
||||
default n
|
||||
|
||||
config SENSORS_ABITUGURU
|
||||
tristate "Abit uGuru"
|
||||
depends on HWMON && EXPERIMENTAL
|
||||
depends on EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the Abit uGuru chips
|
||||
sensor part. The voltage and frequency control parts of the Abit
|
||||
|
@ -39,9 +39,19 @@ config SENSORS_ABITUGURU
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called abituguru.
|
||||
|
||||
config SENSORS_AD7418
|
||||
tristate "Analog Devices AD7416, AD7417 and AD7418"
|
||||
depends on I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the Analog Devices
|
||||
AD7416, AD7417 and AD7418 temperature monitoring chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called ad7418.
|
||||
|
||||
config SENSORS_ADM1021
|
||||
tristate "Analog Devices ADM1021 and compatibles"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for Analog Devices ADM1021
|
||||
and ADM1023 sensor chips and clones: Maxim MAX1617 and MAX1617A,
|
||||
|
@ -53,7 +63,7 @@ config SENSORS_ADM1021
|
|||
|
||||
config SENSORS_ADM1025
|
||||
tristate "Analog Devices ADM1025 and compatibles"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for Analog Devices ADM1025
|
||||
|
@ -64,7 +74,7 @@ config SENSORS_ADM1025
|
|||
|
||||
config SENSORS_ADM1026
|
||||
tristate "Analog Devices ADM1026 and compatibles"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for Analog Devices ADM1026
|
||||
|
@ -75,7 +85,7 @@ config SENSORS_ADM1026
|
|||
|
||||
config SENSORS_ADM1029
|
||||
tristate "Analog Devices ADM1029"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for Analog Devices ADM1029
|
||||
sensor chip.
|
||||
|
@ -86,7 +96,7 @@ config SENSORS_ADM1029
|
|||
|
||||
config SENSORS_ADM1031
|
||||
tristate "Analog Devices ADM1031 and compatibles"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for Analog Devices ADM1031
|
||||
and ADM1030 sensor chips.
|
||||
|
@ -96,7 +106,7 @@ config SENSORS_ADM1031
|
|||
|
||||
config SENSORS_ADM9240
|
||||
tristate "Analog Devices ADM9240 and compatibles"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for Analog Devices ADM9240,
|
||||
|
@ -107,7 +117,7 @@ config SENSORS_ADM9240
|
|||
|
||||
config SENSORS_K8TEMP
|
||||
tristate "AMD Athlon64/FX or Opteron temperature sensor"
|
||||
depends on HWMON && X86 && PCI && EXPERIMENTAL
|
||||
depends on X86 && PCI && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the temperature
|
||||
sensor(s) inside your CPU. Supported is whole AMD K8
|
||||
|
@ -119,7 +129,7 @@ config SENSORS_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
|
||||
depends on 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.
|
||||
|
@ -144,7 +154,7 @@ config SENSORS_AMS_I2C
|
|||
|
||||
config SENSORS_ASB100
|
||||
tristate "Asus ASB100 Bach"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for the ASB100 Bach sensor
|
||||
|
@ -155,7 +165,7 @@ config SENSORS_ASB100
|
|||
|
||||
config SENSORS_ATXP1
|
||||
tristate "Attansic ATXP1 VID controller"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for the Attansic ATXP1 VID
|
||||
|
@ -169,7 +179,7 @@ config SENSORS_ATXP1
|
|||
|
||||
config SENSORS_DS1621
|
||||
tristate "Dallas Semiconductor DS1621 and DS1625"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for Dallas Semiconductor
|
||||
DS1621 and DS1625 sensor chips.
|
||||
|
@ -179,7 +189,7 @@ config SENSORS_DS1621
|
|||
|
||||
config SENSORS_F71805F
|
||||
tristate "Fintek F71805F/FG and F71872F/FG"
|
||||
depends on HWMON && EXPERIMENTAL
|
||||
depends on EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for hardware monitoring
|
||||
features of the Fintek F71805F/FG and F71872F/FG Super-I/O
|
||||
|
@ -190,7 +200,7 @@ config SENSORS_F71805F
|
|||
|
||||
config SENSORS_FSCHER
|
||||
tristate "FSC Hermes"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for Fujitsu Siemens
|
||||
Computers Hermes sensor chips.
|
||||
|
@ -200,7 +210,7 @@ config SENSORS_FSCHER
|
|||
|
||||
config SENSORS_FSCPOS
|
||||
tristate "FSC Poseidon"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for Fujitsu Siemens
|
||||
Computers Poseidon sensor chips.
|
||||
|
@ -210,7 +220,7 @@ config SENSORS_FSCPOS
|
|||
|
||||
config SENSORS_GL518SM
|
||||
tristate "Genesys Logic GL518SM"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for Genesys Logic GL518SM
|
||||
sensor chips.
|
||||
|
@ -220,7 +230,7 @@ config SENSORS_GL518SM
|
|||
|
||||
config SENSORS_GL520SM
|
||||
tristate "Genesys Logic GL520SM"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for Genesys Logic GL520SM
|
||||
|
@ -229,9 +239,17 @@ config SENSORS_GL520SM
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called gl520sm.
|
||||
|
||||
config SENSORS_CORETEMP
|
||||
tristate "Intel Core (2) Duo/Solo temperature sensor"
|
||||
depends on X86 && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the temperature
|
||||
sensor inside your CPU. Supported all are all known variants
|
||||
of Intel Core family.
|
||||
|
||||
config SENSORS_IT87
|
||||
tristate "ITE IT87xx and compatibles"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
select I2C_ISA
|
||||
select HWMON_VID
|
||||
help
|
||||
|
@ -243,7 +261,7 @@ config SENSORS_IT87
|
|||
|
||||
config SENSORS_LM63
|
||||
tristate "National Semiconductor LM63"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for the National Semiconductor
|
||||
LM63 remote diode digital temperature sensor with integrated fan
|
||||
|
@ -255,7 +273,7 @@ config SENSORS_LM63
|
|||
|
||||
config SENSORS_LM70
|
||||
tristate "National Semiconductor LM70"
|
||||
depends on HWMON && SPI_MASTER && EXPERIMENTAL
|
||||
depends on SPI_MASTER && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the National Semiconductor
|
||||
LM70 digital temperature sensor chip.
|
||||
|
@ -265,7 +283,7 @@ config SENSORS_LM70
|
|||
|
||||
config SENSORS_LM75
|
||||
tristate "National Semiconductor LM75 and compatibles"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor LM75
|
||||
sensor chips and clones: Dallas Semiconductor DS75 and DS1775 (in
|
||||
|
@ -280,7 +298,7 @@ config SENSORS_LM75
|
|||
|
||||
config SENSORS_LM77
|
||||
tristate "National Semiconductor LM77"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor LM77
|
||||
sensor chips.
|
||||
|
@ -290,8 +308,7 @@ config SENSORS_LM77
|
|||
|
||||
config SENSORS_LM78
|
||||
tristate "National Semiconductor LM78 and compatibles"
|
||||
depends on HWMON && I2C
|
||||
select I2C_ISA
|
||||
depends on I2C
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor LM78,
|
||||
|
@ -302,7 +319,7 @@ config SENSORS_LM78
|
|||
|
||||
config SENSORS_LM80
|
||||
tristate "National Semiconductor LM80"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor
|
||||
LM80 sensor chips.
|
||||
|
@ -312,7 +329,7 @@ config SENSORS_LM80
|
|||
|
||||
config SENSORS_LM83
|
||||
tristate "National Semiconductor LM83 and compatibles"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor
|
||||
LM82 and LM83 sensor chips.
|
||||
|
@ -322,7 +339,7 @@ config SENSORS_LM83
|
|||
|
||||
config SENSORS_LM85
|
||||
tristate "National Semiconductor LM85 and compatibles"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor LM85
|
||||
|
@ -333,7 +350,7 @@ config SENSORS_LM85
|
|||
|
||||
config SENSORS_LM87
|
||||
tristate "National Semiconductor LM87"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor LM87
|
||||
|
@ -344,7 +361,7 @@ config SENSORS_LM87
|
|||
|
||||
config SENSORS_LM90
|
||||
tristate "National Semiconductor LM90 and compatibles"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor LM90,
|
||||
LM86, LM89 and LM99, Analog Devices ADM1032 and Maxim MAX6657 and
|
||||
|
@ -358,7 +375,7 @@ config SENSORS_LM90
|
|||
|
||||
config SENSORS_LM92
|
||||
tristate "National Semiconductor LM92 and compatibles"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for National Semiconductor LM92
|
||||
and Maxim MAX6635 sensor chips.
|
||||
|
@ -368,16 +385,26 @@ config SENSORS_LM92
|
|||
|
||||
config SENSORS_MAX1619
|
||||
tristate "Maxim MAX1619 sensor chip"
|
||||
depends on HWMON && I2C
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for MAX1619 sensor chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max1619.
|
||||
|
||||
config SENSORS_MAX6650
|
||||
tristate "Maxim MAX6650 sensor chip"
|
||||
depends on I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the MAX6650 / MAX6651
|
||||
sensor chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max6650.
|
||||
|
||||
config SENSORS_PC87360
|
||||
tristate "National Semiconductor PC87360 family"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select I2C_ISA
|
||||
select HWMON_VID
|
||||
help
|
||||
|
@ -392,7 +419,7 @@ config SENSORS_PC87360
|
|||
|
||||
config SENSORS_PC87427
|
||||
tristate "National Semiconductor PC87427"
|
||||
depends on HWMON && EXPERIMENTAL
|
||||
depends on EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get access to the hardware monitoring
|
||||
functions of the National Semiconductor PC87427 Super-I/O chip.
|
||||
|
@ -405,7 +432,7 @@ config SENSORS_PC87427
|
|||
|
||||
config SENSORS_SIS5595
|
||||
tristate "Silicon Integrated Systems Corp. SiS5595"
|
||||
depends on HWMON && I2C && PCI && EXPERIMENTAL
|
||||
depends on I2C && PCI && EXPERIMENTAL
|
||||
select I2C_ISA
|
||||
help
|
||||
If you say yes here you get support for the integrated sensors in
|
||||
|
@ -416,28 +443,28 @@ config SENSORS_SIS5595
|
|||
|
||||
config SENSORS_SMSC47M1
|
||||
tristate "SMSC LPC47M10x and compatibles"
|
||||
depends on HWMON && I2C
|
||||
select I2C_ISA
|
||||
help
|
||||
If you say yes here you get support for the integrated fan
|
||||
monitoring and control capabilities of the SMSC LPC47B27x,
|
||||
LPC47M10x, LPC47M112, LPC47M13x, LPC47M14x, LPC47M15x,
|
||||
LPC47M192 and LPC47M997 chips.
|
||||
LPC47M192, LPC47M292 and LPC47M997 chips.
|
||||
|
||||
The temperature and voltage sensor features of the LPC47M192
|
||||
and LPC47M997 are supported by another driver, select also
|
||||
"SMSC LPC47M192 and compatibles" below for those.
|
||||
The temperature and voltage sensor features of the LPC47M15x,
|
||||
LPC47M192, LPC47M292 and LPC47M997 are supported by another
|
||||
driver, select also "SMSC LPC47M192 and compatibles" below for
|
||||
those.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called smsc47m1.
|
||||
|
||||
config SENSORS_SMSC47M192
|
||||
tristate "SMSC LPC47M192 and compatibles"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for the temperature and
|
||||
voltage sensors of the SMSC LPC47M192 and LPC47M997 chips.
|
||||
voltage sensors of the SMSC LPC47M192, LPC47M15x, LPC47M292
|
||||
and LPC47M997 chips.
|
||||
|
||||
The fan monitoring and control capabilities of these chips
|
||||
are supported by another driver, select
|
||||
|
@ -449,8 +476,7 @@ config SENSORS_SMSC47M192
|
|||
|
||||
config SENSORS_SMSC47B397
|
||||
tristate "SMSC LPC47B397-NC"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
select I2C_ISA
|
||||
depends on EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the SMSC LPC47B397-NC
|
||||
sensor chip.
|
||||
|
@ -460,7 +486,7 @@ config SENSORS_SMSC47B397
|
|||
|
||||
config SENSORS_VIA686A
|
||||
tristate "VIA686A"
|
||||
depends on HWMON && I2C && PCI
|
||||
depends on I2C && PCI
|
||||
select I2C_ISA
|
||||
help
|
||||
If you say yes here you get support for the integrated sensors in
|
||||
|
@ -471,7 +497,7 @@ config SENSORS_VIA686A
|
|||
|
||||
config SENSORS_VT1211
|
||||
tristate "VIA VT1211"
|
||||
depends on HWMON && EXPERIMENTAL
|
||||
depends on EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here then you get support for hardware monitoring
|
||||
|
@ -482,7 +508,7 @@ config SENSORS_VT1211
|
|||
|
||||
config SENSORS_VT8231
|
||||
tristate "VIA VT8231"
|
||||
depends on HWMON && I2C && PCI && EXPERIMENTAL
|
||||
depends on I2C && PCI && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
select I2C_ISA
|
||||
help
|
||||
|
@ -494,8 +520,7 @@ config SENSORS_VT8231
|
|||
|
||||
config SENSORS_W83781D
|
||||
tristate "Winbond W83781D, W83782D, W83783S, W83627HF, Asus AS99127F"
|
||||
depends on HWMON && I2C
|
||||
select I2C_ISA
|
||||
depends on I2C
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for the Winbond W8378x series
|
||||
|
@ -507,7 +532,7 @@ config SENSORS_W83781D
|
|||
|
||||
config SENSORS_W83791D
|
||||
tristate "Winbond W83791D"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for the Winbond W83791D chip.
|
||||
|
@ -517,7 +542,7 @@ config SENSORS_W83791D
|
|||
|
||||
config SENSORS_W83792D
|
||||
tristate "Winbond W83792D"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the Winbond W83792D chip.
|
||||
|
||||
|
@ -526,7 +551,7 @@ config SENSORS_W83792D
|
|||
|
||||
config SENSORS_W83793
|
||||
tristate "Winbond W83793"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for the Winbond W83793
|
||||
|
@ -537,7 +562,7 @@ config SENSORS_W83793
|
|||
|
||||
config SENSORS_W83L785TS
|
||||
tristate "Winbond W83L785TS-S"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the Winbond W83L785TS-S
|
||||
sensor chip, which is used on the Asus A7N8X, among other
|
||||
|
@ -548,8 +573,6 @@ config SENSORS_W83L785TS
|
|||
|
||||
config SENSORS_W83627HF
|
||||
tristate "Winbond W83627HF, W83627THF, W83637HF, W83687THF, W83697HF"
|
||||
depends on HWMON && I2C
|
||||
select I2C_ISA
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for the Winbond W836X7 series
|
||||
|
@ -561,7 +584,7 @@ config SENSORS_W83627HF
|
|||
|
||||
config SENSORS_W83627EHF
|
||||
tristate "Winbond W83627EHF"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
depends on I2C && EXPERIMENTAL
|
||||
select I2C_ISA
|
||||
help
|
||||
If you say yes here you get preliminary support for the hardware
|
||||
|
@ -577,7 +600,7 @@ config SENSORS_W83627EHF
|
|||
|
||||
config SENSORS_HDAPS
|
||||
tristate "IBM Hard Drive Active Protection System (hdaps)"
|
||||
depends on HWMON && INPUT && X86
|
||||
depends on INPUT && X86
|
||||
default n
|
||||
help
|
||||
This driver provides support for the IBM Hard Drive Active Protection
|
||||
|
@ -620,7 +643,6 @@ config SENSORS_APPLESMC
|
|||
|
||||
config HWMON_DEBUG_CHIP
|
||||
bool "Hardware Monitoring Chip debugging messages"
|
||||
depends on HWMON
|
||||
default n
|
||||
help
|
||||
Say Y here if you want the I2C chip drivers to produce a bunch of
|
||||
|
@ -628,4 +650,4 @@ config HWMON_DEBUG_CHIP
|
|||
a problem with I2C support and want to see more of what is going
|
||||
on.
|
||||
|
||||
endmenu
|
||||
endif # HWMON
|
||||
|
|
|
@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
|
|||
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
|
||||
|
||||
obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o
|
||||
obj-$(CONFIG_SENSORS_AD7418) += ad7418.o
|
||||
obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o
|
||||
obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
|
||||
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
|
||||
|
@ -23,6 +24,7 @@ obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
|
|||
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
|
||||
obj-$(CONFIG_SENSORS_AMS) += ams/
|
||||
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
||||
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
|
||||
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
|
||||
obj-$(CONFIG_SENSORS_FSCHER) += fscher.o
|
||||
|
@ -44,6 +46,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o
|
|||
obj-$(CONFIG_SENSORS_LM90) += lm90.o
|
||||
obj-$(CONFIG_SENSORS_LM92) += lm92.o
|
||||
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
||||
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
|
||||
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
||||
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
|
||||
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* An hwmon driver for the Analog Devices AD7416/17/18
|
||||
* Copyright (C) 2006-07 Tower Technologies
|
||||
*
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* Based on lm75.c
|
||||
* Copyright (C) 1998-99 Frodo Looijaard <frodol@dds.nl>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License,
|
||||
* as published by the Free Software Foundation - version 2.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "lm75.h"
|
||||
|
||||
#define DRV_VERSION "0.3"
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x28, I2C_CLIENT_END };
|
||||
/* Insmod parameters */
|
||||
I2C_CLIENT_INSMOD_3(ad7416, ad7417, ad7418);
|
||||
|
||||
/* AD7418 registers */
|
||||
#define AD7418_REG_TEMP_IN 0x00
|
||||
#define AD7418_REG_CONF 0x01
|
||||
#define AD7418_REG_TEMP_HYST 0x02
|
||||
#define AD7418_REG_TEMP_OS 0x03
|
||||
#define AD7418_REG_ADC 0x04
|
||||
#define AD7418_REG_CONF2 0x05
|
||||
|
||||
#define AD7418_REG_ADC_CH(x) ((x) << 5)
|
||||
#define AD7418_CH_TEMP AD7418_REG_ADC_CH(0)
|
||||
|
||||
static const u8 AD7418_REG_TEMP[] = { AD7418_REG_TEMP_IN,
|
||||
AD7418_REG_TEMP_HYST,
|
||||
AD7418_REG_TEMP_OS };
|
||||
|
||||
struct ad7418_data {
|
||||
struct i2c_client client;
|
||||
struct class_device *class_dev;
|
||||
struct attribute_group attrs;
|
||||
enum chips type;
|
||||
struct mutex lock;
|
||||
int adc_max; /* number of ADC channels */
|
||||
char valid;
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
s16 temp[3]; /* Register values */
|
||||
u16 in[4];
|
||||
};
|
||||
|
||||
static int ad7418_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int ad7418_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static int ad7418_detach_client(struct i2c_client *client);
|
||||
|
||||
static struct i2c_driver ad7418_driver = {
|
||||
.driver = {
|
||||
.name = "ad7418",
|
||||
},
|
||||
.attach_adapter = ad7418_attach_adapter,
|
||||
.detach_client = ad7418_detach_client,
|
||||
};
|
||||
|
||||
/* All registers are word-sized, except for the configuration registers.
|
||||
* AD7418 uses a high-byte first convention. Do NOT use those functions to
|
||||
* access the configuration registers CONF and CONF2, as they are byte-sized.
|
||||
*/
|
||||
static inline int ad7418_read(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
return swab16(i2c_smbus_read_word_data(client, reg));
|
||||
}
|
||||
|
||||
static inline int ad7418_write(struct i2c_client *client, u8 reg, u16 value)
|
||||
{
|
||||
return i2c_smbus_write_word_data(client, reg, swab16(value));
|
||||
}
|
||||
|
||||
static void ad7418_init_client(struct i2c_client *client)
|
||||
{
|
||||
struct ad7418_data *data = i2c_get_clientdata(client);
|
||||
|
||||
int reg = i2c_smbus_read_byte_data(client, AD7418_REG_CONF);
|
||||
if (reg < 0) {
|
||||
dev_err(&client->dev, "cannot read configuration register\n");
|
||||
} else {
|
||||
dev_info(&client->dev, "configuring for mode 1\n");
|
||||
i2c_smbus_write_byte_data(client, AD7418_REG_CONF, reg & 0xfe);
|
||||
|
||||
if (data->type == ad7417 || data->type == ad7418)
|
||||
i2c_smbus_write_byte_data(client,
|
||||
AD7418_REG_CONF2, 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
static struct ad7418_data *ad7418_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct ad7418_data *data = i2c_get_clientdata(client);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
u8 cfg;
|
||||
int i, ch;
|
||||
|
||||
/* read config register and clear channel bits */
|
||||
cfg = i2c_smbus_read_byte_data(client, AD7418_REG_CONF);
|
||||
cfg &= 0x1F;
|
||||
|
||||
i2c_smbus_write_byte_data(client, AD7418_REG_CONF,
|
||||
cfg | AD7418_CH_TEMP);
|
||||
udelay(30);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
data->temp[i] = ad7418_read(client, AD7418_REG_TEMP[i]);
|
||||
}
|
||||
|
||||
for (i = 0, ch = 4; i < data->adc_max; i++, ch--) {
|
||||
i2c_smbus_write_byte_data(client,
|
||||
AD7418_REG_CONF,
|
||||
cfg | AD7418_REG_ADC_CH(ch));
|
||||
|
||||
udelay(15);
|
||||
data->in[data->adc_max - 1 - i] =
|
||||
ad7418_read(client, AD7418_REG_ADC);
|
||||
}
|
||||
|
||||
/* restore old configuration value */
|
||||
ad7418_write(client, AD7418_REG_CONF, cfg);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct ad7418_data *data = ad7418_update_device(dev);
|
||||
return sprintf(buf, "%d\n",
|
||||
LM75_TEMP_FROM_REG(data->temp[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t show_adc(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct ad7418_data *data = ad7418_update_device(dev);
|
||||
|
||||
return sprintf(buf, "%d\n",
|
||||
((data->in[attr->index] >> 6) * 2500 + 512) / 1024);
|
||||
}
|
||||
|
||||
static ssize_t set_temp(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct ad7418_data *data = i2c_get_clientdata(client);
|
||||
int temp = simple_strtol(buf, NULL, 10);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->temp[attr->index] = LM75_TEMP_TO_REG(temp);
|
||||
ad7418_write(client, AD7418_REG_TEMP[attr->index], data->temp[attr->index]);
|
||||
mutex_unlock(&data->lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
|
||||
show_temp, set_temp, 1);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
||||
show_temp, set_temp, 2);
|
||||
|
||||
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_adc, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_adc, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_adc, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_adc, NULL, 3);
|
||||
|
||||
static int ad7418_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_probe(adapter, &addr_data, ad7418_detect);
|
||||
}
|
||||
|
||||
static struct attribute *ad7416_attributes[] = {
|
||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute *ad7417_attributes[] = {
|
||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_input.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute *ad7418_attributes[] = {
|
||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int ad7418_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *client;
|
||||
struct ad7418_data *data;
|
||||
int err = 0;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
goto exit;
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct ad7418_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
client = &data->client;
|
||||
client->addr = address;
|
||||
client->adapter = adapter;
|
||||
client->driver = &ad7418_driver;
|
||||
|
||||
i2c_set_clientdata(client, data);
|
||||
|
||||
mutex_init(&data->lock);
|
||||
|
||||
/* AD7418 has a curious behaviour on registers 6 and 7. They
|
||||
* both always read 0xC071 and are not documented on the datasheet.
|
||||
* We use them to detect the chip.
|
||||
*/
|
||||
if (kind <= 0) {
|
||||
int reg, reg6, reg7;
|
||||
|
||||
/* the AD7416 lies within this address range, but I have
|
||||
* no means to check.
|
||||
*/
|
||||
if (address >= 0x48 && address <= 0x4f) {
|
||||
/* XXX add tests for AD7416 here */
|
||||
/* data->type = ad7416; */
|
||||
}
|
||||
/* here we might have AD7417 or AD7418 */
|
||||
else if (address >= 0x28 && address <= 0x2f) {
|
||||
reg6 = i2c_smbus_read_word_data(client, 0x06);
|
||||
reg7 = i2c_smbus_read_word_data(client, 0x07);
|
||||
|
||||
if (address == 0x28 && reg6 == 0xC071 && reg7 == 0xC071)
|
||||
data->type = ad7418;
|
||||
|
||||
/* XXX add tests for AD7417 here */
|
||||
|
||||
|
||||
/* both AD7417 and AD7418 have bits 0-5 of
|
||||
* the CONF2 register at 0
|
||||
*/
|
||||
reg = i2c_smbus_read_byte_data(client,
|
||||
AD7418_REG_CONF2);
|
||||
if (reg & 0x3F)
|
||||
data->type = any_chip; /* detection failed */
|
||||
}
|
||||
} else {
|
||||
dev_dbg(&adapter->dev, "detection forced\n");
|
||||
}
|
||||
|
||||
if (kind > 0)
|
||||
data->type = kind;
|
||||
else if (kind < 0 && data->type == any_chip) {
|
||||
err = -ENODEV;
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
switch (data->type) {
|
||||
case any_chip:
|
||||
case ad7416:
|
||||
data->adc_max = 0;
|
||||
data->attrs.attrs = ad7416_attributes;
|
||||
strlcpy(client->name, "ad7416", I2C_NAME_SIZE);
|
||||
break;
|
||||
|
||||
case ad7417:
|
||||
data->adc_max = 4;
|
||||
data->attrs.attrs = ad7417_attributes;
|
||||
strlcpy(client->name, "ad7417", I2C_NAME_SIZE);
|
||||
break;
|
||||
|
||||
case ad7418:
|
||||
data->adc_max = 1;
|
||||
data->attrs.attrs = ad7418_attributes;
|
||||
strlcpy(client->name, "ad7418", I2C_NAME_SIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = i2c_attach_client(client)))
|
||||
goto exit_free;
|
||||
|
||||
dev_info(&client->dev, "%s chip found\n", client->name);
|
||||
|
||||
/* Initialize the AD7418 chip */
|
||||
ad7418_init_client(client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
if ((err = sysfs_create_group(&client->dev.kobj, &data->attrs)))
|
||||
goto exit_detach;
|
||||
|
||||
data->class_dev = hwmon_device_register(&client->dev);
|
||||
if (IS_ERR(data->class_dev)) {
|
||||
err = PTR_ERR(data->class_dev);
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove:
|
||||
sysfs_remove_group(&client->dev.kobj, &data->attrs);
|
||||
exit_detach:
|
||||
i2c_detach_client(client);
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ad7418_detach_client(struct i2c_client *client)
|
||||
{
|
||||
struct ad7418_data *data = i2c_get_clientdata(client);
|
||||
hwmon_device_unregister(data->class_dev);
|
||||
sysfs_remove_group(&client->dev.kobj, &data->attrs);
|
||||
i2c_detach_client(client);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init ad7418_init(void)
|
||||
{
|
||||
return i2c_add_driver(&ad7418_driver);
|
||||
}
|
||||
|
||||
static void __exit ad7418_exit(void)
|
||||
{
|
||||
i2c_del_driver(&ad7418_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("AD7416/17/18 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
|
||||
module_init(ad7418_init);
|
||||
module_exit(ad7418_exit);
|
|
@ -219,9 +219,6 @@ int __init ams_init(void)
|
|||
/* Found PMU motion sensor */
|
||||
return ams_pmu_init(np);
|
||||
#endif
|
||||
|
||||
printk(KERN_ERR "ams: No motion sensor found.\n");
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
|
|
@ -85,17 +85,17 @@ static int ams_i2c_write(u8 reg, u8 value)
|
|||
static int ams_i2c_cmd(enum ams_i2c_cmd cmd)
|
||||
{
|
||||
s32 result;
|
||||
int remaining = HZ / 20;
|
||||
int count = 3;
|
||||
|
||||
ams_i2c_write(AMS_COMMAND, cmd);
|
||||
mdelay(5);
|
||||
msleep(5);
|
||||
|
||||
while (remaining) {
|
||||
while (count--) {
|
||||
result = ams_i2c_read(AMS_COMMAND);
|
||||
if (result == 0 || result & 0x80)
|
||||
return 0;
|
||||
|
||||
remaining = schedule_timeout(remaining);
|
||||
schedule_timeout_uninterruptible(HZ / 20);
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
|
|
@ -0,0 +1,406 @@
|
|||
/*
|
||||
* coretemp.c - Linux kernel module for hardware monitoring
|
||||
*
|
||||
* Copyright (C) 2007 Rudolf Marek <r.marek@assembler.cz>
|
||||
*
|
||||
* Inspired from many hwmon drivers
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* This 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/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <asm/msr.h>
|
||||
#include <asm/processor.h>
|
||||
|
||||
#define DRVNAME "coretemp"
|
||||
|
||||
typedef enum { SHOW_TEMP, SHOW_TJMAX, SHOW_LABEL, SHOW_NAME } SHOW;
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
*/
|
||||
|
||||
static struct coretemp_data *coretemp_update_device(struct device *dev);
|
||||
|
||||
struct coretemp_data {
|
||||
struct class_device *class_dev;
|
||||
struct mutex update_lock;
|
||||
const char *name;
|
||||
u32 id;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
int temp;
|
||||
int tjmax;
|
||||
u8 alarm;
|
||||
};
|
||||
|
||||
static struct coretemp_data *coretemp_update_device(struct device *dev);
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
int ret;
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct coretemp_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (attr->index == SHOW_NAME)
|
||||
ret = sprintf(buf, "%s\n", data->name);
|
||||
else /* show label */
|
||||
ret = sprintf(buf, "Core %d\n", data->id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t show_alarm(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct coretemp_data *data = coretemp_update_device(dev);
|
||||
/* read the Out-of-spec log, never clear */
|
||||
return sprintf(buf, "%d\n", data->alarm);
|
||||
}
|
||||
|
||||
static ssize_t show_temp(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct coretemp_data *data = coretemp_update_device(dev);
|
||||
int err;
|
||||
|
||||
if (attr->index == SHOW_TEMP)
|
||||
err = data->valid ? sprintf(buf, "%d\n", data->temp) : -EAGAIN;
|
||||
else
|
||||
err = sprintf(buf, "%d\n", data->tjmax);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL,
|
||||
SHOW_TEMP);
|
||||
static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL,
|
||||
SHOW_TJMAX);
|
||||
static DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL);
|
||||
static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL);
|
||||
static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, SHOW_NAME);
|
||||
|
||||
static struct attribute *coretemp_attributes[] = {
|
||||
&sensor_dev_attr_name.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_label.dev_attr.attr,
|
||||
&dev_attr_temp1_crit_alarm.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_crit.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group coretemp_group = {
|
||||
.attrs = coretemp_attributes,
|
||||
};
|
||||
|
||||
static struct coretemp_data *coretemp_update_device(struct device *dev)
|
||||
{
|
||||
struct coretemp_data *data = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (!data->valid || time_after(jiffies, data->last_updated + HZ)) {
|
||||
u32 eax, edx;
|
||||
|
||||
data->valid = 0;
|
||||
rdmsr_on_cpu(data->id, MSR_IA32_THERM_STATUS, &eax, &edx);
|
||||
data->alarm = (eax >> 5) & 1;
|
||||
/* update only if data has been valid */
|
||||
if (eax & 0x80000000) {
|
||||
data->temp = data->tjmax - (((eax >> 16)
|
||||
& 0x7f) * 1000);
|
||||
data->valid = 1;
|
||||
} else {
|
||||
dev_dbg(dev, "Temperature data invalid (0x%x)\n", eax);
|
||||
}
|
||||
data->last_updated = jiffies;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __devinit coretemp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct coretemp_data *data;
|
||||
struct cpuinfo_x86 *c = &(cpu_data)[pdev->id];
|
||||
int err;
|
||||
u32 eax, edx;
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct coretemp_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
dev_err(&pdev->dev, "Out of memory\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data->id = pdev->id;
|
||||
data->name = "coretemp";
|
||||
mutex_init(&data->update_lock);
|
||||
/* Tjmax default is 100 degrees C */
|
||||
data->tjmax = 100000;
|
||||
|
||||
/* test if we can access the THERM_STATUS MSR */
|
||||
err = rdmsr_safe_on_cpu(data->id, MSR_IA32_THERM_STATUS, &eax, &edx);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"Unable to access THERM_STATUS MSR, giving up\n");
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Some processors have Tjmax 85 following magic should detect it
|
||||
Intel won't disclose the information without signed NDA, but
|
||||
individuals cannot sign it. Catch(ed) 22.
|
||||
*/
|
||||
|
||||
if (((c->x86_model == 0xf) && (c->x86_mask > 3)) ||
|
||||
(c->x86_model == 0xe)) {
|
||||
err = rdmsr_safe_on_cpu(data->id, 0xee, &eax, &edx);
|
||||
if (err) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Unable to access MSR 0xEE, Tjmax left at %d "
|
||||
"degrees C\n", data->tjmax/1000);
|
||||
} else if (eax & 0x40000000) {
|
||||
data->tjmax = 85000;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj, &coretemp_group)))
|
||||
goto exit_free;
|
||||
|
||||
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_class;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_class:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &coretemp_group);
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit coretemp_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct coretemp_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
hwmon_device_unregister(data->class_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &coretemp_group);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver coretemp_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = DRVNAME,
|
||||
},
|
||||
.probe = coretemp_probe,
|
||||
.remove = __devexit_p(coretemp_remove),
|
||||
};
|
||||
|
||||
struct pdev_entry {
|
||||
struct list_head list;
|
||||
struct platform_device *pdev;
|
||||
unsigned int cpu;
|
||||
};
|
||||
|
||||
static LIST_HEAD(pdev_list);
|
||||
static DEFINE_MUTEX(pdev_list_mutex);
|
||||
|
||||
static int __cpuinit coretemp_device_add(unsigned int cpu)
|
||||
{
|
||||
int err;
|
||||
struct platform_device *pdev;
|
||||
struct pdev_entry *pdev_entry;
|
||||
|
||||
pdev = platform_device_alloc(DRVNAME, cpu);
|
||||
if (!pdev) {
|
||||
err = -ENOMEM;
|
||||
printk(KERN_ERR DRVNAME ": Device allocation failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL);
|
||||
if (!pdev_entry) {
|
||||
err = -ENOMEM;
|
||||
goto exit_device_put;
|
||||
}
|
||||
|
||||
err = platform_device_add(pdev);
|
||||
if (err) {
|
||||
printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
|
||||
err);
|
||||
goto exit_device_free;
|
||||
}
|
||||
|
||||
pdev_entry->pdev = pdev;
|
||||
pdev_entry->cpu = cpu;
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_add_tail(&pdev_entry->list, &pdev_list);
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_device_free:
|
||||
kfree(pdev_entry);
|
||||
exit_device_put:
|
||||
platform_device_put(pdev);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
void coretemp_device_remove(unsigned int cpu)
|
||||
{
|
||||
struct pdev_entry *p, *n;
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||
if (p->cpu == cpu) {
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
}
|
||||
|
||||
static int coretemp_cpu_callback(struct notifier_block *nfb,
|
||||
unsigned long action, void *hcpu)
|
||||
{
|
||||
unsigned int cpu = (unsigned long) hcpu;
|
||||
|
||||
switch (action) {
|
||||
case CPU_ONLINE:
|
||||
coretemp_device_add(cpu);
|
||||
break;
|
||||
case CPU_DEAD:
|
||||
coretemp_device_remove(cpu);
|
||||
break;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block __cpuinitdata coretemp_cpu_notifier = {
|
||||
.notifier_call = coretemp_cpu_callback,
|
||||
};
|
||||
#endif /* !CONFIG_HOTPLUG_CPU */
|
||||
|
||||
static int __init coretemp_init(void)
|
||||
{
|
||||
int i, err = -ENODEV;
|
||||
struct pdev_entry *p, *n;
|
||||
|
||||
printk(KERN_NOTICE DRVNAME ": This driver uses undocumented features "
|
||||
"of Core CPU. Temperature might be wrong!\n");
|
||||
|
||||
/* quick check if we run Intel */
|
||||
if (cpu_data[0].x86_vendor != X86_VENDOR_INTEL)
|
||||
goto exit;
|
||||
|
||||
err = platform_driver_register(&coretemp_driver);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
for_each_online_cpu(i) {
|
||||
struct cpuinfo_x86 *c = &(cpu_data)[i];
|
||||
|
||||
/* check if family 6, models e, f */
|
||||
if ((c->cpuid_level < 0) || (c->x86 != 0x6) ||
|
||||
!((c->x86_model == 0xe) || (c->x86_model == 0xf))) {
|
||||
|
||||
/* supported CPU not found, but report the unknown
|
||||
family 6 CPU */
|
||||
if ((c->x86 == 0x6) && (c->x86_model > 0xf))
|
||||
printk(KERN_WARNING DRVNAME ": Unknown CPU "
|
||||
"model %x\n", c->x86_model);
|
||||
continue;
|
||||
}
|
||||
|
||||
err = coretemp_device_add(i);
|
||||
if (err)
|
||||
goto exit_devices_unreg;
|
||||
}
|
||||
if (list_empty(&pdev_list)) {
|
||||
err = -ENODEV;
|
||||
goto exit_driver_unreg;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
register_hotcpu_notifier(&coretemp_cpu_notifier);
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
exit_devices_unreg:
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
exit_driver_unreg:
|
||||
platform_driver_unregister(&coretemp_driver);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit coretemp_exit(void)
|
||||
{
|
||||
struct pdev_entry *p, *n;
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
unregister_hotcpu_notifier(&coretemp_cpu_notifier);
|
||||
#endif
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
platform_driver_unregister(&coretemp_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
|
||||
MODULE_DESCRIPTION("Intel Core temperature monitor");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(coretemp_init)
|
||||
module_exit(coretemp_exit)
|
|
@ -35,6 +35,7 @@
|
|||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
static struct platform_device *pdev;
|
||||
|
@ -1140,6 +1141,13 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!request_region(res->start + ADDR_REG_OFFSET, 2, DRVNAME)) {
|
||||
err = -EBUSY;
|
||||
dev_err(&pdev->dev, "Failed to request region 0x%lx-0x%lx\n",
|
||||
(unsigned long)(res->start + ADDR_REG_OFFSET),
|
||||
(unsigned long)(res->start + ADDR_REG_OFFSET + 1));
|
||||
goto exit_free;
|
||||
}
|
||||
data->addr = res->start;
|
||||
data->name = names[sio_data->kind];
|
||||
mutex_init(&data->update_lock);
|
||||
|
@ -1165,7 +1173,7 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
|
|||
|
||||
/* Register sysfs interface files */
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group)))
|
||||
goto exit_free;
|
||||
goto exit_release_region;
|
||||
if (data->has_in & (1 << 4)) { /* in4 */
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj,
|
||||
&f71805f_group_optin[0])))
|
||||
|
@ -1219,6 +1227,8 @@ exit_remove_files:
|
|||
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_release_region:
|
||||
release_region(res->start + ADDR_REG_OFFSET, 2);
|
||||
exit_free:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
|
@ -1229,6 +1239,7 @@ exit:
|
|||
static int __devexit f71805f_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct f71805f_data *data = platform_get_drvdata(pdev);
|
||||
struct resource *res;
|
||||
int i;
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
@ -1239,6 +1250,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev)
|
|||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
|
||||
kfree(data);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_region(res->start + ADDR_REG_OFFSET, 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -166,16 +166,16 @@ static struct vrm_model vrm_models[] = {
|
|||
{X86_VENDOR_INTEL, 0x6, 0xE, ANY, 14}, /* Intel Core (65 nm) */
|
||||
{X86_VENDOR_INTEL, 0x6, 0xF, ANY, 110}, /* Intel Conroe */
|
||||
{X86_VENDOR_INTEL, 0x6, ANY, ANY, 82}, /* any P6 */
|
||||
{X86_VENDOR_INTEL, 0x7, ANY, ANY, 0}, /* Itanium */
|
||||
{X86_VENDOR_INTEL, 0xF, 0x0, ANY, 90}, /* P4 */
|
||||
{X86_VENDOR_INTEL, 0xF, 0x1, ANY, 90}, /* P4 Willamette */
|
||||
{X86_VENDOR_INTEL, 0xF, 0x2, ANY, 90}, /* P4 Northwood */
|
||||
{X86_VENDOR_INTEL, 0xF, ANY, ANY, 100}, /* Prescott and above assume VRD 10 */
|
||||
{X86_VENDOR_INTEL, 0x10, ANY, ANY, 0}, /* Itanium 2 */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0x7, ANY, 85}, /* Eden ESP/Ezra */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0x8, 0x7, 85}, /* Ezra T */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0x9, 0x7, 85}, /* Nemiah */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0x9, ANY, 17}, /* C3-M */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0x9, ANY, 17}, /* C3-M, Eden-N */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0xA, 0x7, 0}, /* No information */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0xA, ANY, 13}, /* C7, Esther */
|
||||
{X86_VENDOR_UNKNOWN, ANY, ANY, ANY, 0} /* stop here */
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include "lm75.h"
|
||||
|
@ -39,10 +40,12 @@ I2C_CLIENT_INSMOD_1(lm75);
|
|||
/* Many LM75 constants specified below */
|
||||
|
||||
/* The LM75 registers */
|
||||
#define LM75_REG_TEMP 0x00
|
||||
#define LM75_REG_CONF 0x01
|
||||
#define LM75_REG_TEMP_HYST 0x02
|
||||
#define LM75_REG_TEMP_OS 0x03
|
||||
static const u8 LM75_REG_TEMP[3] = {
|
||||
0x00, /* input */
|
||||
0x03, /* max */
|
||||
0x02, /* hyst */
|
||||
};
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct lm75_data {
|
||||
|
@ -51,9 +54,10 @@ struct lm75_data {
|
|||
struct mutex update_lock;
|
||||
char valid; /* !=0 if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
u16 temp_input; /* Register values */
|
||||
u16 temp_max;
|
||||
u16 temp_hyst;
|
||||
u16 temp[3]; /* Register values,
|
||||
0 = input
|
||||
1 = max
|
||||
2 = hyst */
|
||||
};
|
||||
|
||||
static int lm75_attach_adapter(struct i2c_adapter *adapter);
|
||||
|
@ -75,35 +79,36 @@ static struct i2c_driver lm75_driver = {
|
|||
.detach_client = lm75_detach_client,
|
||||
};
|
||||
|
||||
#define show(value) \
|
||||
static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct lm75_data *data = lm75_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->value)); \
|
||||
static ssize_t show_temp(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm75_data *data = lm75_update_device(dev);
|
||||
return sprintf(buf, "%d\n",
|
||||
LM75_TEMP_FROM_REG(data->temp[attr->index]));
|
||||
}
|
||||
show(temp_max);
|
||||
show(temp_hyst);
|
||||
show(temp_input);
|
||||
|
||||
#define set(value, reg) \
|
||||
static ssize_t set_##value(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct lm75_data *data = i2c_get_clientdata(client); \
|
||||
int temp = simple_strtoul(buf, NULL, 10); \
|
||||
\
|
||||
mutex_lock(&data->update_lock); \
|
||||
data->value = LM75_TEMP_TO_REG(temp); \
|
||||
lm75_write_value(client, reg, data->value); \
|
||||
mutex_unlock(&data->update_lock); \
|
||||
return count; \
|
||||
static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm75_data *data = i2c_get_clientdata(client);
|
||||
int nr = attr->index;
|
||||
unsigned long temp = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->temp[nr] = LM75_TEMP_TO_REG(temp);
|
||||
lm75_write_value(client, LM75_REG_TEMP[nr], data->temp[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
set(temp_max, LM75_REG_TEMP_OS);
|
||||
set(temp_hyst, LM75_REG_TEMP_HYST);
|
||||
|
||||
static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max);
|
||||
static DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_temp_hyst, set_temp_hyst);
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
||||
show_temp, set_temp, 1);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
|
||||
show_temp, set_temp, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
||||
|
||||
static int lm75_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
|
@ -113,9 +118,9 @@ static int lm75_attach_adapter(struct i2c_adapter *adapter)
|
|||
}
|
||||
|
||||
static struct attribute *lm75_attributes[] = {
|
||||
&dev_attr_temp1_input.attr,
|
||||
&dev_attr_temp1_max.attr,
|
||||
&dev_attr_temp1_max_hyst.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||
|
||||
NULL
|
||||
};
|
||||
|
@ -283,11 +288,12 @@ static struct lm75_data *lm75_update_device(struct device *dev)
|
|||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
int i;
|
||||
dev_dbg(&client->dev, "Starting lm75 update\n");
|
||||
|
||||
data->temp_input = lm75_read_value(client, LM75_REG_TEMP);
|
||||
data->temp_max = lm75_read_value(client, LM75_REG_TEMP_OS);
|
||||
data->temp_hyst = lm75_read_value(client, LM75_REG_TEMP_HYST);
|
||||
for (i = 0; i < ARRAY_SIZE(data->temp); i++)
|
||||
data->temp[i] = lm75_read_value(client,
|
||||
LM75_REG_TEMP[i]);
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
lm78.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
|
||||
Copyright (c) 2007 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 as published by
|
||||
|
@ -23,13 +24,18 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-isa.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-vid.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
/* ISA device, if found */
|
||||
static struct platform_device *pdev;
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x20, 0x21, 0x22, 0x23, 0x24,
|
||||
0x25, 0x26, 0x27, 0x28, 0x29,
|
||||
|
@ -121,12 +127,8 @@ static inline int TEMP_FROM_REG(s8 val)
|
|||
a bit - except if there could be more than one SMBus. Groan. No solution
|
||||
for this yet. */
|
||||
|
||||
/* This module may seem overly long and complicated. In fact, it is not so
|
||||
bad. Quite a lot of bookkeeping is done. A real driver can often cut
|
||||
some corners. */
|
||||
|
||||
/* For each registered chip, we need to keep some data in memory.
|
||||
The structure is dynamically allocated. */
|
||||
/* For ISA chips, we abuse the i2c_client addr and name fields. We also use
|
||||
the driver field to differentiate between I2C and ISA chips. */
|
||||
struct lm78_data {
|
||||
struct i2c_client client;
|
||||
struct class_device *class_dev;
|
||||
|
@ -152,14 +154,16 @@ struct lm78_data {
|
|||
|
||||
|
||||
static int lm78_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int lm78_isa_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int lm78_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static int lm78_detach_client(struct i2c_client *client);
|
||||
|
||||
static int lm78_read_value(struct i2c_client *client, u8 reg);
|
||||
static int lm78_write_value(struct i2c_client *client, u8 reg, u8 value);
|
||||
static int __devinit lm78_isa_probe(struct platform_device *pdev);
|
||||
static int __devexit lm78_isa_remove(struct platform_device *pdev);
|
||||
|
||||
static int lm78_read_value(struct lm78_data *data, u8 reg);
|
||||
static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value);
|
||||
static struct lm78_data *lm78_update_device(struct device *dev);
|
||||
static void lm78_init_client(struct i2c_client *client);
|
||||
static void lm78_init_device(struct lm78_data *data);
|
||||
|
||||
|
||||
static struct i2c_driver lm78_driver = {
|
||||
|
@ -171,95 +175,78 @@ static struct i2c_driver lm78_driver = {
|
|||
.detach_client = lm78_detach_client,
|
||||
};
|
||||
|
||||
static struct i2c_driver lm78_isa_driver = {
|
||||
static struct platform_driver lm78_isa_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm78-isa",
|
||||
.name = "lm78",
|
||||
},
|
||||
.attach_adapter = lm78_isa_attach_adapter,
|
||||
.detach_client = lm78_detach_client,
|
||||
.probe = lm78_isa_probe,
|
||||
.remove = lm78_isa_remove,
|
||||
};
|
||||
|
||||
|
||||
/* 7 Voltages */
|
||||
static ssize_t show_in(struct device *dev, char *buf, int nr)
|
||||
static ssize_t show_in(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in[nr]));
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t show_in_min(struct device *dev, char *buf, int nr)
|
||||
static ssize_t show_in_min(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[nr]));
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t show_in_max(struct device *dev, char *buf, int nr)
|
||||
static ssize_t show_in_max(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[nr]));
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t set_in_min(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
static ssize_t set_in_min(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = dev_get_drvdata(dev);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
int nr = attr->index;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->in_min[nr] = IN_TO_REG(val);
|
||||
lm78_write_value(client, LM78_REG_IN_MIN(nr), data->in_min[nr]);
|
||||
lm78_write_value(data, LM78_REG_IN_MIN(nr), data->in_min[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_in_max(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
static ssize_t set_in_max(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = dev_get_drvdata(dev);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
int nr = attr->index;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->in_max[nr] = IN_TO_REG(val);
|
||||
lm78_write_value(client, LM78_REG_IN_MAX(nr), data->in_max[nr]);
|
||||
lm78_write_value(data, LM78_REG_IN_MAX(nr), data->in_max[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define show_in_offset(offset) \
|
||||
static ssize_t \
|
||||
show_in##offset (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_in(dev, buf, offset); \
|
||||
} \
|
||||
static DEVICE_ATTR(in##offset##_input, S_IRUGO, \
|
||||
show_in##offset, NULL); \
|
||||
static ssize_t \
|
||||
show_in##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_in_min(dev, buf, offset); \
|
||||
} \
|
||||
static ssize_t \
|
||||
show_in##offset##_max (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_in_max(dev, buf, offset); \
|
||||
} \
|
||||
static ssize_t set_in##offset##_min (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_in_min(dev, buf, count, offset); \
|
||||
} \
|
||||
static ssize_t set_in##offset##_max (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_in_max(dev, buf, count, offset); \
|
||||
} \
|
||||
static DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
show_in##offset##_min, set_in##offset##_min); \
|
||||
static DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \
|
||||
show_in##offset##_max, set_in##offset##_max);
|
||||
static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \
|
||||
show_in, NULL, offset); \
|
||||
static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
show_in_min, set_in_min, offset); \
|
||||
static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \
|
||||
show_in_max, set_in_max, offset);
|
||||
|
||||
show_in_offset(0);
|
||||
show_in_offset(1);
|
||||
|
@ -270,46 +257,49 @@ show_in_offset(5);
|
|||
show_in_offset(6);
|
||||
|
||||
/* Temperature */
|
||||
static ssize_t show_temp(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
static ssize_t show_temp(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp));
|
||||
}
|
||||
|
||||
static ssize_t show_temp_over(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
static ssize_t show_temp_over(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_over));
|
||||
}
|
||||
|
||||
static ssize_t set_temp_over(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
static ssize_t set_temp_over(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct lm78_data *data = dev_get_drvdata(dev);
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->temp_over = TEMP_TO_REG(val);
|
||||
lm78_write_value(client, LM78_REG_TEMP_OVER, data->temp_over);
|
||||
lm78_write_value(data, LM78_REG_TEMP_OVER, data->temp_over);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_temp_hyst(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
static ssize_t show_temp_hyst(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_hyst));
|
||||
}
|
||||
|
||||
static ssize_t set_temp_hyst(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
static ssize_t set_temp_hyst(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct lm78_data *data = dev_get_drvdata(dev);
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->temp_hyst = TEMP_TO_REG(val);
|
||||
lm78_write_value(client, LM78_REG_TEMP_HYST, data->temp_hyst);
|
||||
lm78_write_value(data, LM78_REG_TEMP_HYST, data->temp_hyst);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
@ -321,49 +311,59 @@ static DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR,
|
|||
show_temp_hyst, set_temp_hyst);
|
||||
|
||||
/* 3 Fans */
|
||||
static ssize_t show_fan(struct device *dev, char *buf, int nr)
|
||||
static ssize_t show_fan(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
int nr = attr->index;
|
||||
return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr],
|
||||
DIV_FROM_REG(data->fan_div[nr])) );
|
||||
}
|
||||
|
||||
static ssize_t show_fan_min(struct device *dev, char *buf, int nr)
|
||||
static ssize_t show_fan_min(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
int nr = attr->index;
|
||||
return sprintf(buf,"%d\n", FAN_FROM_REG(data->fan_min[nr],
|
||||
DIV_FROM_REG(data->fan_div[nr])) );
|
||||
}
|
||||
|
||||
static ssize_t set_fan_min(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
static ssize_t set_fan_min(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = dev_get_drvdata(dev);
|
||||
int nr = attr->index;
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr]));
|
||||
lm78_write_value(client, LM78_REG_FAN_MIN(nr), data->fan_min[nr]);
|
||||
lm78_write_value(data, LM78_REG_FAN_MIN(nr), data->fan_min[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_fan_div(struct device *dev, char *buf, int nr)
|
||||
static ssize_t show_fan_div(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr]) );
|
||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[attr->index]));
|
||||
}
|
||||
|
||||
/* Note: we save and restore the fan minimum here, because its value is
|
||||
determined in part by the fan divisor. This follows the principle of
|
||||
least surprise; the user doesn't expect the fan minimum to change just
|
||||
because the divisor changed. */
|
||||
static ssize_t set_fan_div(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
static ssize_t set_fan_div(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct lm78_data *data = dev_get_drvdata(dev);
|
||||
int nr = attr->index;
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
unsigned long min;
|
||||
u8 reg;
|
||||
|
@ -378,13 +378,13 @@ static ssize_t set_fan_div(struct device *dev, const char *buf,
|
|||
case 4: data->fan_div[nr] = 2; break;
|
||||
case 8: data->fan_div[nr] = 3; break;
|
||||
default:
|
||||
dev_err(&client->dev, "fan_div value %ld not "
|
||||
dev_err(dev, "fan_div value %ld not "
|
||||
"supported. Choose one of 1, 2, 4 or 8!\n", val);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
reg = lm78_read_value(client, LM78_REG_VID_FANDIV);
|
||||
reg = lm78_read_value(data, LM78_REG_VID_FANDIV);
|
||||
switch (nr) {
|
||||
case 0:
|
||||
reg = (reg & 0xcf) | (data->fan_div[nr] << 4);
|
||||
|
@ -393,63 +393,36 @@ static ssize_t set_fan_div(struct device *dev, const char *buf,
|
|||
reg = (reg & 0x3f) | (data->fan_div[nr] << 6);
|
||||
break;
|
||||
}
|
||||
lm78_write_value(client, LM78_REG_VID_FANDIV, reg);
|
||||
lm78_write_value(data, LM78_REG_VID_FANDIV, reg);
|
||||
|
||||
data->fan_min[nr] =
|
||||
FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr]));
|
||||
lm78_write_value(client, LM78_REG_FAN_MIN(nr), data->fan_min[nr]);
|
||||
lm78_write_value(data, LM78_REG_FAN_MIN(nr), data->fan_min[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#define show_fan_offset(offset) \
|
||||
static ssize_t show_fan_##offset (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_fan(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t show_fan_##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_fan_min(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t show_fan_##offset##_div (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_fan_div(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_fan_##offset##_min (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_fan_min(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static DEVICE_ATTR(fan##offset##_input, S_IRUGO, show_fan_##offset, NULL);\
|
||||
static DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
show_fan_##offset##_min, set_fan_##offset##_min);
|
||||
|
||||
static ssize_t set_fan_1_div(struct device *dev, struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
return set_fan_div(dev, buf, count, 0) ;
|
||||
}
|
||||
|
||||
static ssize_t set_fan_2_div(struct device *dev, struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
return set_fan_div(dev, buf, count, 1) ;
|
||||
}
|
||||
#define show_fan_offset(offset) \
|
||||
static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \
|
||||
show_fan, NULL, offset - 1); \
|
||||
static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
show_fan_min, set_fan_min, offset - 1);
|
||||
|
||||
show_fan_offset(1);
|
||||
show_fan_offset(2);
|
||||
show_fan_offset(3);
|
||||
|
||||
/* Fan 3 divisor is locked in H/W */
|
||||
static DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR,
|
||||
show_fan_1_div, set_fan_1_div);
|
||||
static DEVICE_ATTR(fan2_div, S_IRUGO | S_IWUSR,
|
||||
show_fan_2_div, set_fan_2_div);
|
||||
static DEVICE_ATTR(fan3_div, S_IRUGO, show_fan_3_div, NULL);
|
||||
static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR,
|
||||
show_fan_div, set_fan_div, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_div, S_IRUGO | S_IWUSR,
|
||||
show_fan_div, set_fan_div, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan3_div, S_IRUGO, show_fan_div, NULL, 2);
|
||||
|
||||
/* VID */
|
||||
static ssize_t show_vid(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
static ssize_t show_vid(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", vid_from_reg(data->vid, 82));
|
||||
|
@ -457,7 +430,8 @@ static ssize_t show_vid(struct device *dev, struct device_attribute *attr, char
|
|||
static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL);
|
||||
|
||||
/* Alarms */
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%u\n", data->alarms);
|
||||
|
@ -475,45 +449,40 @@ static int lm78_attach_adapter(struct i2c_adapter *adapter)
|
|||
return i2c_probe(adapter, &addr_data, lm78_detect);
|
||||
}
|
||||
|
||||
static int lm78_isa_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
return lm78_detect(adapter, isa_address, -1);
|
||||
}
|
||||
|
||||
static struct attribute *lm78_attributes[] = {
|
||||
&dev_attr_in0_input.attr,
|
||||
&dev_attr_in0_min.attr,
|
||||
&dev_attr_in0_max.attr,
|
||||
&dev_attr_in1_input.attr,
|
||||
&dev_attr_in1_min.attr,
|
||||
&dev_attr_in1_max.attr,
|
||||
&dev_attr_in2_input.attr,
|
||||
&dev_attr_in2_min.attr,
|
||||
&dev_attr_in2_max.attr,
|
||||
&dev_attr_in3_input.attr,
|
||||
&dev_attr_in3_min.attr,
|
||||
&dev_attr_in3_max.attr,
|
||||
&dev_attr_in4_input.attr,
|
||||
&dev_attr_in4_min.attr,
|
||||
&dev_attr_in4_max.attr,
|
||||
&dev_attr_in5_input.attr,
|
||||
&dev_attr_in5_min.attr,
|
||||
&dev_attr_in5_max.attr,
|
||||
&dev_attr_in6_input.attr,
|
||||
&dev_attr_in6_min.attr,
|
||||
&dev_attr_in6_max.attr,
|
||||
&sensor_dev_attr_in0_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in0_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in0_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_max.dev_attr.attr,
|
||||
&dev_attr_temp1_input.attr,
|
||||
&dev_attr_temp1_max.attr,
|
||||
&dev_attr_temp1_max_hyst.attr,
|
||||
&dev_attr_fan1_input.attr,
|
||||
&dev_attr_fan1_min.attr,
|
||||
&dev_attr_fan1_div.attr,
|
||||
&dev_attr_fan2_input.attr,
|
||||
&dev_attr_fan2_min.attr,
|
||||
&dev_attr_fan2_div.attr,
|
||||
&dev_attr_fan3_input.attr,
|
||||
&dev_attr_fan3_min.attr,
|
||||
&dev_attr_fan3_div.attr,
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_div.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_div.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_div.dev_attr.attr,
|
||||
&dev_attr_alarms.attr,
|
||||
&dev_attr_cpu0_vid.attr,
|
||||
|
||||
|
@ -524,6 +493,17 @@ static const struct attribute_group lm78_group = {
|
|||
.attrs = lm78_attributes,
|
||||
};
|
||||
|
||||
/* I2C devices get this name attribute automatically, but for ISA devices
|
||||
we must create it by ourselves. */
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct lm78_data *data = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%s\n", data->client.name);
|
||||
}
|
||||
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
|
||||
|
||||
/* This function is called by i2c_probe */
|
||||
static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
|
@ -531,54 +511,10 @@ static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
|
|||
struct i2c_client *new_client;
|
||||
struct lm78_data *data;
|
||||
const char *client_name = "";
|
||||
int is_isa = i2c_is_isa_adapter(adapter);
|
||||
|
||||
if (!is_isa &&
|
||||
!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
err = -ENODEV;
|
||||
goto ERROR0;
|
||||
}
|
||||
|
||||
/* Reserve the ISA region */
|
||||
if (is_isa)
|
||||
if (!request_region(address, LM78_EXTENT,
|
||||
lm78_isa_driver.driver.name)) {
|
||||
err = -EBUSY;
|
||||
goto ERROR0;
|
||||
}
|
||||
|
||||
/* Probe whether there is anything available on this address. Already
|
||||
done for SMBus clients */
|
||||
if (kind < 0) {
|
||||
if (is_isa) {
|
||||
|
||||
#define REALLY_SLOW_IO
|
||||
/* We need the timeouts for at least some LM78-like
|
||||
chips. But only if we read 'undefined' registers. */
|
||||
i = inb_p(address + 1);
|
||||
if (inb_p(address + 2) != i) {
|
||||
err = -ENODEV;
|
||||
goto ERROR1;
|
||||
}
|
||||
if (inb_p(address + 3) != i) {
|
||||
err = -ENODEV;
|
||||
goto ERROR1;
|
||||
}
|
||||
if (inb_p(address + 7) != i) {
|
||||
err = -ENODEV;
|
||||
goto ERROR1;
|
||||
}
|
||||
#undef REALLY_SLOW_IO
|
||||
|
||||
/* Let's just hope nothing breaks here */
|
||||
i = inb_p(address + 5) & 0x7f;
|
||||
outb_p(~i & 0x7f, address + 5);
|
||||
if ((inb_p(address + 5) & 0x7f) != (~i & 0x7f)) {
|
||||
outb_p(i, address + 5);
|
||||
err = -ENODEV;
|
||||
goto ERROR1;
|
||||
}
|
||||
}
|
||||
goto ERROR1;
|
||||
}
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
|
@ -591,22 +527,19 @@ static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
|
|||
}
|
||||
|
||||
new_client = &data->client;
|
||||
if (is_isa)
|
||||
mutex_init(&data->lock);
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = is_isa ? &lm78_isa_driver : &lm78_driver;
|
||||
new_client->flags = 0;
|
||||
new_client->driver = &lm78_driver;
|
||||
|
||||
/* Now, we do the remaining detection. */
|
||||
if (kind < 0) {
|
||||
if (lm78_read_value(new_client, LM78_REG_CONFIG) & 0x80) {
|
||||
if (lm78_read_value(data, LM78_REG_CONFIG) & 0x80) {
|
||||
err = -ENODEV;
|
||||
goto ERROR2;
|
||||
}
|
||||
if (!is_isa && (lm78_read_value(
|
||||
new_client, LM78_REG_I2C_ADDR) != address)) {
|
||||
if (lm78_read_value(data, LM78_REG_I2C_ADDR) !=
|
||||
address) {
|
||||
err = -ENODEV;
|
||||
goto ERROR2;
|
||||
}
|
||||
|
@ -614,7 +547,7 @@ static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
|
|||
|
||||
/* Determine the chip type. */
|
||||
if (kind <= 0) {
|
||||
i = lm78_read_value(new_client, LM78_REG_CHIPID);
|
||||
i = lm78_read_value(data, LM78_REG_CHIPID);
|
||||
if (i == 0x00 || i == 0x20 /* LM78 */
|
||||
|| i == 0x40) /* LM78-J */
|
||||
kind = lm78;
|
||||
|
@ -641,21 +574,12 @@ static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
|
|||
strlcpy(new_client->name, client_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;
|
||||
|
||||
/* Initialize the LM78 chip */
|
||||
lm78_init_client(new_client);
|
||||
|
||||
/* A few vars need to be filled upon startup */
|
||||
for (i = 0; i < 3; i++) {
|
||||
data->fan_min[i] = lm78_read_value(new_client,
|
||||
LM78_REG_FAN_MIN(i));
|
||||
}
|
||||
lm78_init_device(data);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
if ((err = sysfs_create_group(&new_client->dev.kobj, &lm78_group)))
|
||||
|
@ -676,9 +600,6 @@ ERROR3:
|
|||
ERROR2:
|
||||
kfree(data);
|
||||
ERROR1:
|
||||
if (is_isa)
|
||||
release_region(address, LM78_EXTENT);
|
||||
ERROR0:
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -693,9 +614,77 @@ static int lm78_detach_client(struct i2c_client *client)
|
|||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
if(i2c_is_isa_client(client))
|
||||
release_region(client->addr, LM78_EXTENT);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit lm78_isa_probe(struct platform_device *pdev)
|
||||
{
|
||||
int err;
|
||||
struct lm78_data *data;
|
||||
struct resource *res;
|
||||
const char *name;
|
||||
|
||||
/* Reserve the ISA region */
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!request_region(res->start, LM78_EXTENT, "lm78")) {
|
||||
err = -EBUSY;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct lm78_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit_release_region;
|
||||
}
|
||||
mutex_init(&data->lock);
|
||||
data->client.addr = res->start;
|
||||
i2c_set_clientdata(&data->client, data);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
if (lm78_read_value(data, LM78_REG_CHIPID) & 0x80) {
|
||||
data->type = lm79;
|
||||
name = "lm79";
|
||||
} else {
|
||||
data->type = lm78;
|
||||
name = "lm78";
|
||||
}
|
||||
strlcpy(data->client.name, name, I2C_NAME_SIZE);
|
||||
|
||||
/* Initialize the LM78 chip */
|
||||
lm78_init_device(data);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj, &lm78_group))
|
||||
|| (err = device_create_file(&pdev->dev, &dev_attr_name)))
|
||||
goto exit_remove_files;
|
||||
|
||||
data->class_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->class_dev)) {
|
||||
err = PTR_ERR(data->class_dev);
|
||||
goto exit_remove_files;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove_files:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
|
||||
device_remove_file(&pdev->dev, &dev_attr_name);
|
||||
kfree(data);
|
||||
exit_release_region:
|
||||
release_region(res->start, LM78_EXTENT);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit lm78_isa_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct lm78_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
hwmon_device_unregister(data->class_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
|
||||
device_remove_file(&pdev->dev, &dev_attr_name);
|
||||
release_region(data->client.addr, LM78_EXTENT);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
|
@ -706,11 +695,12 @@ static int lm78_detach_client(struct i2c_client *client)
|
|||
separately.
|
||||
We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks,
|
||||
would slow down the LM78 access and should not be necessary. */
|
||||
static int lm78_read_value(struct i2c_client *client, u8 reg)
|
||||
static int lm78_read_value(struct lm78_data *data, u8 reg)
|
||||
{
|
||||
int res;
|
||||
if (i2c_is_isa_client(client)) {
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct i2c_client *client = &data->client;
|
||||
|
||||
if (!client->driver) { /* ISA device */
|
||||
int res;
|
||||
mutex_lock(&data->lock);
|
||||
outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET);
|
||||
res = inb_p(client->addr + LM78_DATA_REG_OFFSET);
|
||||
|
@ -727,10 +717,11 @@ static int lm78_read_value(struct i2c_client *client, u8 reg)
|
|||
would slow down the LM78 access and should not be necessary.
|
||||
There are some ugly typecasts here, but the good new is - they should
|
||||
nowhere else be necessary! */
|
||||
static int lm78_write_value(struct i2c_client *client, u8 reg, u8 value)
|
||||
static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value)
|
||||
{
|
||||
if (i2c_is_isa_client(client)) {
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct i2c_client *client = &data->client;
|
||||
|
||||
if (!client->driver) { /* ISA device */
|
||||
mutex_lock(&data->lock);
|
||||
outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET);
|
||||
outb_p(value, client->addr + LM78_DATA_REG_OFFSET);
|
||||
|
@ -740,20 +731,29 @@ static int lm78_write_value(struct i2c_client *client, u8 reg, u8 value)
|
|||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
static void lm78_init_client(struct i2c_client *client)
|
||||
static void lm78_init_device(struct lm78_data *data)
|
||||
{
|
||||
u8 config = lm78_read_value(client, LM78_REG_CONFIG);
|
||||
u8 config;
|
||||
int i;
|
||||
|
||||
/* Start monitoring */
|
||||
if (!(config & 0x01))
|
||||
lm78_write_value(client, LM78_REG_CONFIG,
|
||||
config = lm78_read_value(data, LM78_REG_CONFIG);
|
||||
if ((config & 0x09) != 0x01)
|
||||
lm78_write_value(data, LM78_REG_CONFIG,
|
||||
(config & 0xf7) | 0x01);
|
||||
|
||||
/* A few vars need to be filled upon startup */
|
||||
for (i = 0; i < 3; i++) {
|
||||
data->fan_min[i] = lm78_read_value(data,
|
||||
LM78_REG_FAN_MIN(i));
|
||||
}
|
||||
|
||||
mutex_init(&data->update_lock);
|
||||
}
|
||||
|
||||
static struct lm78_data *lm78_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
struct lm78_data *data = dev_get_drvdata(dev);
|
||||
int i;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
@ -761,39 +761,39 @@ static struct lm78_data *lm78_update_device(struct device *dev)
|
|||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
|
||||
dev_dbg(&client->dev, "Starting lm78 update\n");
|
||||
dev_dbg(dev, "Starting lm78 update\n");
|
||||
|
||||
for (i = 0; i <= 6; i++) {
|
||||
data->in[i] =
|
||||
lm78_read_value(client, LM78_REG_IN(i));
|
||||
lm78_read_value(data, LM78_REG_IN(i));
|
||||
data->in_min[i] =
|
||||
lm78_read_value(client, LM78_REG_IN_MIN(i));
|
||||
lm78_read_value(data, LM78_REG_IN_MIN(i));
|
||||
data->in_max[i] =
|
||||
lm78_read_value(client, LM78_REG_IN_MAX(i));
|
||||
lm78_read_value(data, LM78_REG_IN_MAX(i));
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
data->fan[i] =
|
||||
lm78_read_value(client, LM78_REG_FAN(i));
|
||||
lm78_read_value(data, LM78_REG_FAN(i));
|
||||
data->fan_min[i] =
|
||||
lm78_read_value(client, LM78_REG_FAN_MIN(i));
|
||||
lm78_read_value(data, LM78_REG_FAN_MIN(i));
|
||||
}
|
||||
data->temp = lm78_read_value(client, LM78_REG_TEMP);
|
||||
data->temp = lm78_read_value(data, LM78_REG_TEMP);
|
||||
data->temp_over =
|
||||
lm78_read_value(client, LM78_REG_TEMP_OVER);
|
||||
lm78_read_value(data, LM78_REG_TEMP_OVER);
|
||||
data->temp_hyst =
|
||||
lm78_read_value(client, LM78_REG_TEMP_HYST);
|
||||
i = lm78_read_value(client, LM78_REG_VID_FANDIV);
|
||||
lm78_read_value(data, LM78_REG_TEMP_HYST);
|
||||
i = lm78_read_value(data, LM78_REG_VID_FANDIV);
|
||||
data->vid = i & 0x0f;
|
||||
if (data->type == lm79)
|
||||
data->vid |=
|
||||
(lm78_read_value(client, LM78_REG_CHIPID) &
|
||||
(lm78_read_value(data, LM78_REG_CHIPID) &
|
||||
0x01) << 4;
|
||||
else
|
||||
data->vid |= 0x10;
|
||||
data->fan_div[0] = (i >> 4) & 0x03;
|
||||
data->fan_div[1] = i >> 6;
|
||||
data->alarms = lm78_read_value(client, LM78_REG_ALARM1) +
|
||||
(lm78_read_value(client, LM78_REG_ALARM2) << 8);
|
||||
data->alarms = lm78_read_value(data, LM78_REG_ALARM1) +
|
||||
(lm78_read_value(data, LM78_REG_ALARM2) << 8);
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
|
||||
|
@ -805,26 +805,154 @@ static struct lm78_data *lm78_update_device(struct device *dev)
|
|||
return data;
|
||||
}
|
||||
|
||||
/* return 1 if a supported chip is found, 0 otherwise */
|
||||
static int __init lm78_isa_found(unsigned short address)
|
||||
{
|
||||
int val, save, found = 0;
|
||||
|
||||
if (!request_region(address, LM78_EXTENT, "lm78"))
|
||||
return 0;
|
||||
|
||||
#define REALLY_SLOW_IO
|
||||
/* We need the timeouts for at least some LM78-like
|
||||
chips. But only if we read 'undefined' registers. */
|
||||
val = inb_p(address + 1);
|
||||
if (inb_p(address + 2) != val
|
||||
|| inb_p(address + 3) != val
|
||||
|| inb_p(address + 7) != val)
|
||||
goto release;
|
||||
#undef REALLY_SLOW_IO
|
||||
|
||||
/* We should be able to change the 7 LSB of the address port. The
|
||||
MSB (busy flag) should be clear initially, set after the write. */
|
||||
save = inb_p(address + LM78_ADDR_REG_OFFSET);
|
||||
if (save & 0x80)
|
||||
goto release;
|
||||
val = ~save & 0x7f;
|
||||
outb_p(val, address + LM78_ADDR_REG_OFFSET);
|
||||
if (inb_p(address + LM78_ADDR_REG_OFFSET) != (val | 0x80)) {
|
||||
outb_p(save, address + LM78_ADDR_REG_OFFSET);
|
||||
goto release;
|
||||
}
|
||||
|
||||
/* We found a device, now see if it could be an LM78 */
|
||||
outb_p(LM78_REG_CONFIG, address + LM78_ADDR_REG_OFFSET);
|
||||
val = inb_p(address + LM78_DATA_REG_OFFSET);
|
||||
if (val & 0x80)
|
||||
goto release;
|
||||
outb_p(LM78_REG_I2C_ADDR, address + LM78_ADDR_REG_OFFSET);
|
||||
val = inb_p(address + LM78_DATA_REG_OFFSET);
|
||||
if (val < 0x03 || val > 0x77) /* Not a valid I2C address */
|
||||
goto release;
|
||||
|
||||
/* The busy flag should be clear again */
|
||||
if (inb_p(address + LM78_ADDR_REG_OFFSET) & 0x80)
|
||||
goto release;
|
||||
|
||||
/* Explicitly prevent the misdetection of Winbond chips */
|
||||
outb_p(0x4f, address + LM78_ADDR_REG_OFFSET);
|
||||
val = inb_p(address + LM78_DATA_REG_OFFSET);
|
||||
if (val == 0xa3 || val == 0x5c)
|
||||
goto release;
|
||||
|
||||
/* Explicitly prevent the misdetection of ITE chips */
|
||||
outb_p(0x58, address + LM78_ADDR_REG_OFFSET);
|
||||
val = inb_p(address + LM78_DATA_REG_OFFSET);
|
||||
if (val == 0x90)
|
||||
goto release;
|
||||
|
||||
/* Determine the chip type */
|
||||
outb_p(LM78_REG_CHIPID, address + LM78_ADDR_REG_OFFSET);
|
||||
val = inb_p(address + LM78_DATA_REG_OFFSET);
|
||||
if (val == 0x00 /* LM78 */
|
||||
|| val == 0x40 /* LM78-J */
|
||||
|| (val & 0xfe) == 0xc0) /* LM79 */
|
||||
found = 1;
|
||||
|
||||
if (found)
|
||||
pr_info("lm78: Found an %s chip at %#x\n",
|
||||
val & 0x80 ? "LM79" : "LM78", (int)address);
|
||||
|
||||
release:
|
||||
release_region(address, LM78_EXTENT);
|
||||
return found;
|
||||
}
|
||||
|
||||
static int __init lm78_isa_device_add(unsigned short address)
|
||||
{
|
||||
struct resource res = {
|
||||
.start = address,
|
||||
.end = address + LM78_EXTENT,
|
||||
.name = "lm78",
|
||||
.flags = IORESOURCE_IO,
|
||||
};
|
||||
int err;
|
||||
|
||||
pdev = platform_device_alloc("lm78", address);
|
||||
if (!pdev) {
|
||||
err = -ENOMEM;
|
||||
printk(KERN_ERR "lm78: Device allocation failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
err = platform_device_add_resources(pdev, &res, 1);
|
||||
if (err) {
|
||||
printk(KERN_ERR "lm78: Device resource addition failed "
|
||||
"(%d)\n", err);
|
||||
goto exit_device_put;
|
||||
}
|
||||
|
||||
err = platform_device_add(pdev);
|
||||
if (err) {
|
||||
printk(KERN_ERR "lm78: Device addition failed (%d)\n",
|
||||
err);
|
||||
goto exit_device_put;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_device_put:
|
||||
platform_device_put(pdev);
|
||||
exit:
|
||||
pdev = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init sm_lm78_init(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = i2c_add_driver(&lm78_driver);
|
||||
if (res)
|
||||
return res;
|
||||
goto exit;
|
||||
|
||||
/* Don't exit if this one fails, we still want the I2C variants
|
||||
to work! */
|
||||
if (i2c_isa_add_driver(&lm78_isa_driver))
|
||||
isa_address = 0;
|
||||
if (lm78_isa_found(isa_address)) {
|
||||
res = platform_driver_register(&lm78_isa_driver);
|
||||
if (res)
|
||||
goto exit_unreg_i2c_driver;
|
||||
|
||||
/* Sets global pdev as a side effect */
|
||||
res = lm78_isa_device_add(isa_address);
|
||||
if (res)
|
||||
goto exit_unreg_isa_driver;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_unreg_isa_driver:
|
||||
platform_driver_unregister(&lm78_isa_driver);
|
||||
exit_unreg_i2c_driver:
|
||||
i2c_del_driver(&lm78_driver);
|
||||
exit:
|
||||
return res;
|
||||
}
|
||||
|
||||
static void __exit sm_lm78_exit(void)
|
||||
{
|
||||
if (isa_address)
|
||||
i2c_isa_del_driver(&lm78_isa_driver);
|
||||
if (pdev) {
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&lm78_isa_driver);
|
||||
}
|
||||
i2c_del_driver(&lm78_driver);
|
||||
}
|
||||
|
||||
|
|
|
@ -747,6 +747,7 @@ static int lm87_detect(struct i2c_adapter *adapter, int address, int kind)
|
|||
}
|
||||
|
||||
if (!(data->channel & CHAN_NO_VID)) {
|
||||
data->vrm = vid_which_vrm();
|
||||
if ((err = device_create_file(&new_client->dev,
|
||||
&dev_attr_cpu0_vid))
|
||||
|| (err = device_create_file(&new_client->dev,
|
||||
|
@ -779,7 +780,6 @@ static void lm87_init_client(struct i2c_client *client)
|
|||
u8 config;
|
||||
|
||||
data->channel = lm87_read_value(client, LM87_REG_CHANNEL_MODE);
|
||||
data->vrm = vid_which_vrm();
|
||||
|
||||
config = lm87_read_value(client, LM87_REG_CONFIG);
|
||||
if (!(config & 0x01)) {
|
||||
|
|
|
@ -0,0 +1,693 @@
|
|||
/*
|
||||
* max6650.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
* monitoring.
|
||||
*
|
||||
* (C) 2007 by Hans J. Koch <hjk@linutronix.de>
|
||||
*
|
||||
* based on code written by John Morris <john.morris@spirentcom.com>
|
||||
* Copyright (c) 2003 Spirent Communications
|
||||
* and Claus Gindhart <claus.gindhart@kontron.com>
|
||||
*
|
||||
* This module has only been tested with the MAX6650 chip. It should
|
||||
* also work with the MAX6651. It does not distinguish max6650 and max6651
|
||||
* chips.
|
||||
*
|
||||
* Tha datasheet was last seen at:
|
||||
*
|
||||
* http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
/*
|
||||
* Addresses to scan. There are four disjoint possibilities, by pin config.
|
||||
*/
|
||||
|
||||
static unsigned short normal_i2c[] = {0x1b, 0x1f, 0x48, 0x4b, I2C_CLIENT_END};
|
||||
|
||||
/*
|
||||
* Insmod parameters
|
||||
*/
|
||||
|
||||
/* fan_voltage: 5=5V fan, 12=12V fan, 0=don't change */
|
||||
static int fan_voltage;
|
||||
/* prescaler: Possible values are 1, 2, 4, 8, 16 or 0 for don't change */
|
||||
static int prescaler;
|
||||
/* clock: The clock frequency of the chip the driver should assume */
|
||||
static int clock = 254000;
|
||||
|
||||
module_param(fan_voltage, int, S_IRUGO);
|
||||
module_param(prescaler, int, S_IRUGO);
|
||||
module_param(clock, int, S_IRUGO);
|
||||
|
||||
I2C_CLIENT_INSMOD_1(max6650);
|
||||
|
||||
/*
|
||||
* MAX 6650/6651 registers
|
||||
*/
|
||||
|
||||
#define MAX6650_REG_SPEED 0x00
|
||||
#define MAX6650_REG_CONFIG 0x02
|
||||
#define MAX6650_REG_GPIO_DEF 0x04
|
||||
#define MAX6650_REG_DAC 0x06
|
||||
#define MAX6650_REG_ALARM_EN 0x08
|
||||
#define MAX6650_REG_ALARM 0x0A
|
||||
#define MAX6650_REG_TACH0 0x0C
|
||||
#define MAX6650_REG_TACH1 0x0E
|
||||
#define MAX6650_REG_TACH2 0x10
|
||||
#define MAX6650_REG_TACH3 0x12
|
||||
#define MAX6650_REG_GPIO_STAT 0x14
|
||||
#define MAX6650_REG_COUNT 0x16
|
||||
|
||||
/*
|
||||
* Config register bits
|
||||
*/
|
||||
|
||||
#define MAX6650_CFG_V12 0x08
|
||||
#define MAX6650_CFG_PRESCALER_MASK 0x07
|
||||
#define MAX6650_CFG_PRESCALER_2 0x01
|
||||
#define MAX6650_CFG_PRESCALER_4 0x02
|
||||
#define MAX6650_CFG_PRESCALER_8 0x03
|
||||
#define MAX6650_CFG_PRESCALER_16 0x04
|
||||
#define MAX6650_CFG_MODE_MASK 0x30
|
||||
#define MAX6650_CFG_MODE_ON 0x00
|
||||
#define MAX6650_CFG_MODE_OFF 0x10
|
||||
#define MAX6650_CFG_MODE_CLOSED_LOOP 0x20
|
||||
#define MAX6650_CFG_MODE_OPEN_LOOP 0x30
|
||||
#define MAX6650_COUNT_MASK 0x03
|
||||
|
||||
/* Minimum and maximum values of the FAN-RPM */
|
||||
#define FAN_RPM_MIN 240
|
||||
#define FAN_RPM_MAX 30000
|
||||
|
||||
#define DIV_FROM_REG(reg) (1 << (reg & 7))
|
||||
|
||||
static int max6650_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int max6650_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static int max6650_init_client(struct i2c_client *client);
|
||||
static int max6650_detach_client(struct i2c_client *client);
|
||||
static struct max6650_data *max6650_update_device(struct device *dev);
|
||||
|
||||
/*
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
|
||||
static struct i2c_driver max6650_driver = {
|
||||
.driver = {
|
||||
.name = "max6650",
|
||||
},
|
||||
.attach_adapter = max6650_attach_adapter,
|
||||
.detach_client = max6650_detach_client,
|
||||
};
|
||||
|
||||
/*
|
||||
* Client data (each client gets its own)
|
||||
*/
|
||||
|
||||
struct max6650_data
|
||||
{
|
||||
struct i2c_client client;
|
||||
struct class_device *class_dev;
|
||||
struct mutex update_lock;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
|
||||
/* register values */
|
||||
u8 speed;
|
||||
u8 config;
|
||||
u8 tach[4];
|
||||
u8 count;
|
||||
u8 dac;
|
||||
};
|
||||
|
||||
static ssize_t get_fan(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct max6650_data *data = max6650_update_device(dev);
|
||||
int rpm;
|
||||
|
||||
/*
|
||||
* Calculation details:
|
||||
*
|
||||
* Each tachometer counts over an interval given by the "count"
|
||||
* register (0.25, 0.5, 1 or 2 seconds). This module assumes
|
||||
* that the fans produce two pulses per revolution (this seems
|
||||
* to be the most common).
|
||||
*/
|
||||
|
||||
rpm = ((data->tach[attr->index] * 120) / DIV_FROM_REG(data->count));
|
||||
return sprintf(buf, "%d\n", rpm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the fan speed to the specified RPM (or read back the RPM setting).
|
||||
* This works in closed loop mode only. Use pwm1 for open loop speed setting.
|
||||
*
|
||||
* The MAX6650/1 will automatically control fan speed when in closed loop
|
||||
* mode.
|
||||
*
|
||||
* Assumptions:
|
||||
*
|
||||
* 1) The MAX6650/1 internal 254kHz clock frequency is set correctly. Use
|
||||
* the clock module parameter if you need to fine tune this.
|
||||
*
|
||||
* 2) The prescaler (low three bits of the config register) has already
|
||||
* been set to an appropriate value. Use the prescaler module parameter
|
||||
* if your BIOS doesn't initialize the chip properly.
|
||||
*
|
||||
* The relevant equations are given on pages 21 and 22 of the datasheet.
|
||||
*
|
||||
* From the datasheet, the relevant equation when in regulation is:
|
||||
*
|
||||
* [fCLK / (128 x (KTACH + 1))] = 2 x FanSpeed / KSCALE
|
||||
*
|
||||
* where:
|
||||
*
|
||||
* fCLK is the oscillator frequency (either the 254kHz internal
|
||||
* oscillator or the externally applied clock)
|
||||
*
|
||||
* KTACH is the value in the speed register
|
||||
*
|
||||
* FanSpeed is the speed of the fan in rps
|
||||
*
|
||||
* KSCALE is the prescaler value (1, 2, 4, 8, or 16)
|
||||
*
|
||||
* When reading, we need to solve for FanSpeed. When writing, we need to
|
||||
* solve for KTACH.
|
||||
*
|
||||
* Note: this tachometer is completely separate from the tachometers
|
||||
* used to measure the fan speeds. Only one fan's speed (fan1) is
|
||||
* controlled.
|
||||
*/
|
||||
|
||||
static ssize_t get_target(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct max6650_data *data = max6650_update_device(dev);
|
||||
int kscale, ktach, rpm;
|
||||
|
||||
/*
|
||||
* Use the datasheet equation:
|
||||
*
|
||||
* FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)]
|
||||
*
|
||||
* then multiply by 60 to give rpm.
|
||||
*/
|
||||
|
||||
kscale = DIV_FROM_REG(data->config);
|
||||
ktach = data->speed;
|
||||
rpm = 60 * kscale * clock / (256 * (ktach + 1));
|
||||
return sprintf(buf, "%d\n", rpm);
|
||||
}
|
||||
|
||||
static ssize_t set_target(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max6650_data *data = i2c_get_clientdata(client);
|
||||
int rpm = simple_strtoul(buf, NULL, 10);
|
||||
int kscale, ktach;
|
||||
|
||||
rpm = SENSORS_LIMIT(rpm, FAN_RPM_MIN, FAN_RPM_MAX);
|
||||
|
||||
/*
|
||||
* Divide the required speed by 60 to get from rpm to rps, then
|
||||
* use the datasheet equation:
|
||||
*
|
||||
* KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1
|
||||
*/
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
kscale = DIV_FROM_REG(data->config);
|
||||
ktach = ((clock * kscale) / (256 * rpm / 60)) - 1;
|
||||
if (ktach < 0)
|
||||
ktach = 0;
|
||||
if (ktach > 255)
|
||||
ktach = 255;
|
||||
data->speed = ktach;
|
||||
|
||||
i2c_smbus_write_byte_data(client, MAX6650_REG_SPEED, data->speed);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get/set the fan speed in open loop mode using pwm1 sysfs file.
|
||||
* Speed is given as a relative value from 0 to 255, where 255 is maximum
|
||||
* speed. Note that this is done by writing directly to the chip's DAC,
|
||||
* it won't change the closed loop speed set by fan1_target.
|
||||
* Also note that due to rounding errors it is possible that you don't read
|
||||
* back exactly the value you have set.
|
||||
*/
|
||||
|
||||
static ssize_t get_pwm(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
int pwm;
|
||||
struct max6650_data *data = max6650_update_device(dev);
|
||||
|
||||
/* Useful range for dac is 0-180 for 12V fans and 0-76 for 5V fans.
|
||||
Lower DAC values mean higher speeds. */
|
||||
if (data->config & MAX6650_CFG_V12)
|
||||
pwm = 255 - (255 * (int)data->dac)/180;
|
||||
else
|
||||
pwm = 255 - (255 * (int)data->dac)/76;
|
||||
|
||||
if (pwm < 0)
|
||||
pwm = 0;
|
||||
|
||||
return sprintf(buf, "%d\n", pwm);
|
||||
}
|
||||
|
||||
static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max6650_data *data = i2c_get_clientdata(client);
|
||||
int pwm = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
pwm = SENSORS_LIMIT(pwm, 0, 255);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (data->config & MAX6650_CFG_V12)
|
||||
data->dac = 180 - (180 * pwm)/255;
|
||||
else
|
||||
data->dac = 76 - (76 * pwm)/255;
|
||||
|
||||
i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, data->dac);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get/Set controller mode:
|
||||
* Possible values:
|
||||
* 0 = Fan always on
|
||||
* 1 = Open loop, Voltage is set according to speed, not regulated.
|
||||
* 2 = Closed loop, RPM for all fans regulated by fan1 tachometer
|
||||
*/
|
||||
|
||||
static ssize_t get_enable(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct max6650_data *data = max6650_update_device(dev);
|
||||
int mode = (data->config & MAX6650_CFG_MODE_MASK) >> 4;
|
||||
int sysfs_modes[4] = {0, 1, 2, 1};
|
||||
|
||||
return sprintf(buf, "%d\n", sysfs_modes[mode]);
|
||||
}
|
||||
|
||||
static ssize_t set_enable(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max6650_data *data = i2c_get_clientdata(client);
|
||||
int mode = simple_strtoul(buf, NULL, 10);
|
||||
int max6650_modes[3] = {0, 3, 2};
|
||||
|
||||
if ((mode < 0)||(mode > 2)) {
|
||||
dev_err(&client->dev,
|
||||
"illegal value for pwm1_enable (%d)\n", mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
data->config = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG);
|
||||
data->config = (data->config & ~MAX6650_CFG_MODE_MASK)
|
||||
| (max6650_modes[mode] << 4);
|
||||
|
||||
i2c_smbus_write_byte_data(client, MAX6650_REG_CONFIG, data->config);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read/write functions for fan1_div sysfs file. The MAX6650 has no such
|
||||
* divider. We handle this by converting between divider and counttime:
|
||||
*
|
||||
* (counttime == k) <==> (divider == 2^k), k = 0, 1, 2, or 3
|
||||
*
|
||||
* Lower values of k allow to connect a faster fan without the risk of
|
||||
* counter overflow. The price is lower resolution. You can also set counttime
|
||||
* using the module parameter. Note that the module parameter "prescaler" also
|
||||
* influences the behaviour. Unfortunately, there's no sysfs attribute
|
||||
* defined for that. See the data sheet for details.
|
||||
*/
|
||||
|
||||
static ssize_t get_div(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct max6650_data *data = max6650_update_device(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->count));
|
||||
}
|
||||
|
||||
static ssize_t set_div(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max6650_data *data = i2c_get_clientdata(client);
|
||||
int div = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
switch (div) {
|
||||
case 1:
|
||||
data->count = 0;
|
||||
break;
|
||||
case 2:
|
||||
data->count = 1;
|
||||
break;
|
||||
case 4:
|
||||
data->count = 2;
|
||||
break;
|
||||
case 8:
|
||||
data->count = 3;
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev,
|
||||
"illegal value for fan divider (%d)\n", div);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
i2c_smbus_write_byte_data(client, MAX6650_REG_COUNT, data->count);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, get_fan, NULL, 3);
|
||||
static DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO, get_target, set_target);
|
||||
static DEVICE_ATTR(fan1_div, S_IWUSR | S_IRUGO, get_div, set_div);
|
||||
static DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, get_enable, set_enable);
|
||||
static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm);
|
||||
|
||||
|
||||
static struct attribute *max6650_attrs[] = {
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan4_input.dev_attr.attr,
|
||||
&dev_attr_fan1_target.attr,
|
||||
&dev_attr_fan1_div.attr,
|
||||
&dev_attr_pwm1_enable.attr,
|
||||
&dev_attr_pwm1.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group max6650_attr_grp = {
|
||||
.attrs = max6650_attrs,
|
||||
};
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static int max6650_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON)) {
|
||||
dev_dbg(&adapter->dev,
|
||||
"FATAL: max6650_attach_adapter class HWMON not set\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return i2c_probe(adapter, &addr_data, max6650_detect);
|
||||
}
|
||||
|
||||
/*
|
||||
* The following function does more than just detection. If detection
|
||||
* succeeds, it also registers the new chip.
|
||||
*/
|
||||
|
||||
static int max6650_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *client;
|
||||
struct max6650_data *data;
|
||||
int err = -ENODEV;
|
||||
|
||||
dev_dbg(&adapter->dev, "max6650_detect called, kind = %d\n", kind);
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
dev_dbg(&adapter->dev, "max6650: I2C bus doesn't support "
|
||||
"byte read mode, skipping.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct max6650_data), GFP_KERNEL))) {
|
||||
dev_err(&adapter->dev, "max6650: out of memory.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
client = &data->client;
|
||||
i2c_set_clientdata(client, data);
|
||||
client->addr = address;
|
||||
client->adapter = adapter;
|
||||
client->driver = &max6650_driver;
|
||||
|
||||
/*
|
||||
* Now we do the remaining detection. A negative kind means that
|
||||
* the driver was loaded with no force parameter (default), so we
|
||||
* must both detect and identify the chip (actually there is only
|
||||
* one possible kind of chip for now, max6650). A zero kind means that
|
||||
* the driver was loaded with the force parameter, the detection
|
||||
* step shall be skipped. A positive kind means that the driver
|
||||
* was loaded with the force parameter and a given kind of chip is
|
||||
* requested, so both the detection and the identification steps
|
||||
* are skipped.
|
||||
*
|
||||
* Currently I can find no way to distinguish between a MAX6650 and
|
||||
* a MAX6651. This driver has only been tried on the former.
|
||||
*/
|
||||
|
||||
if ((kind < 0) &&
|
||||
( (i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG) & 0xC0)
|
||||
||(i2c_smbus_read_byte_data(client, MAX6650_REG_GPIO_STAT) & 0xE0)
|
||||
||(i2c_smbus_read_byte_data(client, MAX6650_REG_ALARM_EN) & 0xE0)
|
||||
||(i2c_smbus_read_byte_data(client, MAX6650_REG_ALARM) & 0xE0)
|
||||
||(i2c_smbus_read_byte_data(client, MAX6650_REG_COUNT) & 0xFC))) {
|
||||
dev_dbg(&adapter->dev,
|
||||
"max6650: detection failed at 0x%02x.\n", address);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
dev_info(&adapter->dev, "max6650: chip found at 0x%02x.\n", address);
|
||||
|
||||
strlcpy(client->name, "max6650", I2C_NAME_SIZE);
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
if ((err = i2c_attach_client(client))) {
|
||||
dev_err(&adapter->dev, "max6650: failed to attach client.\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the max6650 chip
|
||||
*/
|
||||
if (max6650_init_client(client))
|
||||
goto err_detach;
|
||||
|
||||
err = sysfs_create_group(&client->dev.kobj, &max6650_attr_grp);
|
||||
if (err)
|
||||
goto err_detach;
|
||||
|
||||
data->class_dev = hwmon_device_register(&client->dev);
|
||||
if (!IS_ERR(data->class_dev))
|
||||
return 0;
|
||||
|
||||
err = PTR_ERR(data->class_dev);
|
||||
dev_err(&client->dev, "error registering hwmon device.\n");
|
||||
sysfs_remove_group(&client->dev.kobj, &max6650_attr_grp);
|
||||
err_detach:
|
||||
i2c_detach_client(client);
|
||||
err_free:
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int max6650_detach_client(struct i2c_client *client)
|
||||
{
|
||||
struct max6650_data *data = i2c_get_clientdata(client);
|
||||
int err;
|
||||
|
||||
sysfs_remove_group(&client->dev.kobj, &max6650_attr_grp);
|
||||
hwmon_device_unregister(data->class_dev);
|
||||
err = i2c_detach_client(client);
|
||||
if (!err)
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int max6650_init_client(struct i2c_client *client)
|
||||
{
|
||||
struct max6650_data *data = i2c_get_clientdata(client);
|
||||
int config;
|
||||
int err = -EIO;
|
||||
|
||||
config = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG);
|
||||
|
||||
if (config < 0) {
|
||||
dev_err(&client->dev, "Error reading config, aborting.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
switch (fan_voltage) {
|
||||
case 0:
|
||||
break;
|
||||
case 5:
|
||||
config &= ~MAX6650_CFG_V12;
|
||||
break;
|
||||
case 12:
|
||||
config |= MAX6650_CFG_V12;
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev,
|
||||
"illegal value for fan_voltage (%d)\n",
|
||||
fan_voltage);
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "Fan voltage is set to %dV.\n",
|
||||
(config & MAX6650_CFG_V12) ? 12 : 5);
|
||||
|
||||
switch (prescaler) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
config &= ~MAX6650_CFG_PRESCALER_MASK;
|
||||
break;
|
||||
case 2:
|
||||
config = (config & ~MAX6650_CFG_PRESCALER_MASK)
|
||||
| MAX6650_CFG_PRESCALER_2;
|
||||
break;
|
||||
case 4:
|
||||
config = (config & ~MAX6650_CFG_PRESCALER_MASK)
|
||||
| MAX6650_CFG_PRESCALER_4;
|
||||
break;
|
||||
case 8:
|
||||
config = (config & ~MAX6650_CFG_PRESCALER_MASK)
|
||||
| MAX6650_CFG_PRESCALER_8;
|
||||
break;
|
||||
case 16:
|
||||
config = (config & ~MAX6650_CFG_PRESCALER_MASK)
|
||||
| MAX6650_CFG_PRESCALER_16;
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev,
|
||||
"illegal value for prescaler (%d)\n",
|
||||
prescaler);
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "Prescaler is set to %d.\n",
|
||||
1 << (config & MAX6650_CFG_PRESCALER_MASK));
|
||||
|
||||
/* If mode is set to "full off", we change it to "open loop" and
|
||||
* set DAC to 255, which has the same effect. We do this because
|
||||
* there's no "full off" mode defined in hwmon specifcations.
|
||||
*/
|
||||
|
||||
if ((config & MAX6650_CFG_MODE_MASK) == MAX6650_CFG_MODE_OFF) {
|
||||
dev_dbg(&client->dev, "Change mode to open loop, full off.\n");
|
||||
config = (config & ~MAX6650_CFG_MODE_MASK)
|
||||
| MAX6650_CFG_MODE_OPEN_LOOP;
|
||||
if (i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, 255)) {
|
||||
dev_err(&client->dev, "DAC write error, aborting.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (i2c_smbus_write_byte_data(client, MAX6650_REG_CONFIG, config)) {
|
||||
dev_err(&client->dev, "Config write error, aborting.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
data->config = config;
|
||||
data->count = i2c_smbus_read_byte_data(client, MAX6650_REG_COUNT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const u8 tach_reg[] = {
|
||||
MAX6650_REG_TACH0,
|
||||
MAX6650_REG_TACH1,
|
||||
MAX6650_REG_TACH2,
|
||||
MAX6650_REG_TACH3,
|
||||
};
|
||||
|
||||
static struct max6650_data *max6650_update_device(struct device *dev)
|
||||
{
|
||||
int i;
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max6650_data *data = i2c_get_clientdata(client);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
||||
data->speed = i2c_smbus_read_byte_data(client,
|
||||
MAX6650_REG_SPEED);
|
||||
data->config = i2c_smbus_read_byte_data(client,
|
||||
MAX6650_REG_CONFIG);
|
||||
for (i = 0; i < 4; i++) {
|
||||
data->tach[i] = i2c_smbus_read_byte_data(client,
|
||||
tach_reg[i]);
|
||||
}
|
||||
data->count = i2c_smbus_read_byte_data(client,
|
||||
MAX6650_REG_COUNT);
|
||||
data->dac = i2c_smbus_read_byte_data(client, MAX6650_REG_DAC);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_max6650_init(void)
|
||||
{
|
||||
return i2c_add_driver(&max6650_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_max6650_exit(void)
|
||||
{
|
||||
i2c_del_driver(&max6650_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Hans J. Koch");
|
||||
MODULE_DESCRIPTION("MAX6650 sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_max6650_init);
|
||||
module_exit(sensors_max6650_exit);
|
|
@ -31,6 +31,7 @@
|
|||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
static struct platform_device *pdev;
|
||||
|
@ -429,6 +430,12 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
|
|||
/* This will need to be revisited when we add support for
|
||||
temperature and voltage monitoring. */
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!request_region(res->start, res->end - res->start + 1, DRVNAME)) {
|
||||
err = -EBUSY;
|
||||
dev_err(&pdev->dev, "Failed to request region 0x%lx-0x%lx\n",
|
||||
(unsigned long)res->start, (unsigned long)res->end);
|
||||
goto exit_kfree;
|
||||
}
|
||||
data->address[0] = res->start;
|
||||
|
||||
mutex_init(&data->lock);
|
||||
|
@ -438,7 +445,7 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
|
|||
|
||||
/* Register sysfs hooks */
|
||||
if ((err = device_create_file(&pdev->dev, &dev_attr_name)))
|
||||
goto exit_kfree;
|
||||
goto exit_release_region;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (!(data->fan_enabled & (1 << i)))
|
||||
continue;
|
||||
|
@ -462,6 +469,8 @@ exit_remove_files:
|
|||
continue;
|
||||
sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
|
||||
}
|
||||
exit_release_region:
|
||||
release_region(res->start, res->end - res->start + 1);
|
||||
exit_kfree:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
|
@ -472,6 +481,7 @@ exit:
|
|||
static int __devexit pc87427_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pc87427_data *data = platform_get_drvdata(pdev);
|
||||
struct resource *res;
|
||||
int i;
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
@ -484,6 +494,9 @@ static int __devexit pc87427_remove(struct platform_device *pdev)
|
|||
}
|
||||
kfree(data);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_region(res->start, res->end - res->start + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,16 +30,17 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-isa.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
/* Address is autodetected, there is no default value */
|
||||
static unsigned short address;
|
||||
static struct platform_device *pdev;
|
||||
|
||||
#define DRVNAME "smsc47b397"
|
||||
|
||||
/* Super-I/0 registers and commands */
|
||||
|
||||
|
@ -91,7 +92,8 @@ static u8 smsc47b397_reg_temp[] = {0x25, 0x26, 0x27, 0x80};
|
|||
#define SMSC47B397_REG_FAN_MSB(nr) (0x29 + 2 * (nr))
|
||||
|
||||
struct smsc47b397_data {
|
||||
struct i2c_client client;
|
||||
unsigned short addr;
|
||||
const char *name;
|
||||
struct class_device *class_dev;
|
||||
struct mutex lock;
|
||||
|
||||
|
@ -104,45 +106,43 @@ struct smsc47b397_data {
|
|||
u8 temp[4];
|
||||
};
|
||||
|
||||
static int smsc47b397_read_value(struct i2c_client *client, u8 reg)
|
||||
static int smsc47b397_read_value(struct smsc47b397_data* data, u8 reg)
|
||||
{
|
||||
struct smsc47b397_data *data = i2c_get_clientdata(client);
|
||||
int res;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
outb(reg, client->addr);
|
||||
res = inb_p(client->addr + 1);
|
||||
outb(reg, data->addr);
|
||||
res = inb_p(data->addr + 1);
|
||||
mutex_unlock(&data->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
static struct smsc47b397_data *smsc47b397_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct smsc47b397_data *data = i2c_get_clientdata(client);
|
||||
struct smsc47b397_data *data = dev_get_drvdata(dev);
|
||||
int i;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
||||
dev_dbg(&client->dev, "starting device update...\n");
|
||||
dev_dbg(dev, "starting device update...\n");
|
||||
|
||||
/* 4 temperature inputs, 4 fan inputs */
|
||||
for (i = 0; i < 4; i++) {
|
||||
data->temp[i] = smsc47b397_read_value(client,
|
||||
data->temp[i] = smsc47b397_read_value(data,
|
||||
SMSC47B397_REG_TEMP(i));
|
||||
|
||||
/* must read LSB first */
|
||||
data->fan[i] = smsc47b397_read_value(client,
|
||||
data->fan[i] = smsc47b397_read_value(data,
|
||||
SMSC47B397_REG_FAN_LSB(i));
|
||||
data->fan[i] |= smsc47b397_read_value(client,
|
||||
data->fan[i] |= smsc47b397_read_value(data,
|
||||
SMSC47B397_REG_FAN_MSB(i)) << 8;
|
||||
}
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
|
||||
dev_dbg(&client->dev, "... device update complete\n");
|
||||
dev_dbg(dev, "... device update complete\n");
|
||||
}
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
@ -157,24 +157,18 @@ static int temp_from_reg(u8 reg)
|
|||
return (s8)reg * 1000;
|
||||
}
|
||||
|
||||
/* 0 <= nr <= 3 */
|
||||
static ssize_t show_temp(struct device *dev, char *buf, int nr)
|
||||
static ssize_t show_temp(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47b397_data *data = smsc47b397_update_device(dev);
|
||||
return sprintf(buf, "%d\n", temp_from_reg(data->temp[nr]));
|
||||
return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index]));
|
||||
}
|
||||
|
||||
#define sysfs_temp(num) \
|
||||
static ssize_t show_temp##num(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_temp(dev, buf, num-1); \
|
||||
} \
|
||||
static DEVICE_ATTR(temp##num##_input, S_IRUGO, show_temp##num, NULL)
|
||||
|
||||
sysfs_temp(1);
|
||||
sysfs_temp(2);
|
||||
sysfs_temp(3);
|
||||
sysfs_temp(4);
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3);
|
||||
|
||||
/* FAN: 1 RPM/bit
|
||||
REG: count of 90kHz pulses / revolution */
|
||||
|
@ -183,35 +177,37 @@ static int fan_from_reg(u16 reg)
|
|||
return 90000 * 60 / reg;
|
||||
}
|
||||
|
||||
/* 0 <= nr <= 3 */
|
||||
static ssize_t show_fan(struct device *dev, char *buf, int nr)
|
||||
static ssize_t show_fan(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct smsc47b397_data *data = smsc47b397_update_device(dev);
|
||||
return sprintf(buf, "%d\n", fan_from_reg(data->fan[nr]));
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47b397_data *data = smsc47b397_update_device(dev);
|
||||
return sprintf(buf, "%d\n", fan_from_reg(data->fan[attr->index]));
|
||||
}
|
||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3);
|
||||
|
||||
#define sysfs_fan(num) \
|
||||
static ssize_t show_fan##num(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_fan(dev, buf, num-1); \
|
||||
} \
|
||||
static DEVICE_ATTR(fan##num##_input, S_IRUGO, show_fan##num, NULL)
|
||||
|
||||
sysfs_fan(1);
|
||||
sysfs_fan(2);
|
||||
sysfs_fan(3);
|
||||
sysfs_fan(4);
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct smsc47b397_data *data = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%s\n", data->name);
|
||||
}
|
||||
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
|
||||
|
||||
static struct attribute *smsc47b397_attributes[] = {
|
||||
&dev_attr_temp1_input.attr,
|
||||
&dev_attr_temp2_input.attr,
|
||||
&dev_attr_temp3_input.attr,
|
||||
&dev_attr_temp4_input.attr,
|
||||
&dev_attr_fan1_input.attr,
|
||||
&dev_attr_fan2_input.attr,
|
||||
&dev_attr_fan3_input.attr,
|
||||
&dev_attr_fan4_input.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp4_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan4_input.dev_attr.attr,
|
||||
|
||||
&dev_attr_name.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -219,44 +215,44 @@ static const struct attribute_group smsc47b397_group = {
|
|||
.attrs = smsc47b397_attributes,
|
||||
};
|
||||
|
||||
static int smsc47b397_detach_client(struct i2c_client *client)
|
||||
static int __devexit smsc47b397_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct smsc47b397_data *data = i2c_get_clientdata(client);
|
||||
int err;
|
||||
struct smsc47b397_data *data = platform_get_drvdata(pdev);
|
||||
struct resource *res;
|
||||
|
||||
hwmon_device_unregister(data->class_dev);
|
||||
sysfs_remove_group(&client->dev.kobj, &smsc47b397_group);
|
||||
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
release_region(client->addr, SMSC_EXTENT);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &smsc47b397_group);
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_region(res->start, SMSC_EXTENT);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smsc47b397_detect(struct i2c_adapter *adapter);
|
||||
static int smsc47b397_probe(struct platform_device *pdev);
|
||||
|
||||
static struct i2c_driver smsc47b397_driver = {
|
||||
static struct platform_driver smsc47b397_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "smsc47b397",
|
||||
.name = DRVNAME,
|
||||
},
|
||||
.attach_adapter = smsc47b397_detect,
|
||||
.detach_client = smsc47b397_detach_client,
|
||||
.probe = smsc47b397_probe,
|
||||
.remove = __devexit_p(smsc47b397_remove),
|
||||
};
|
||||
|
||||
static int smsc47b397_detect(struct i2c_adapter *adapter)
|
||||
static int __devinit smsc47b397_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct smsc47b397_data *data;
|
||||
struct resource *res;
|
||||
int err = 0;
|
||||
|
||||
if (!request_region(address, SMSC_EXTENT,
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!request_region(res->start, SMSC_EXTENT,
|
||||
smsc47b397_driver.driver.name)) {
|
||||
dev_err(&adapter->dev, "Region 0x%x already in use!\n",
|
||||
address);
|
||||
dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
|
||||
(unsigned long)res->start,
|
||||
(unsigned long)res->start + SMSC_EXTENT - 1);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
|
@ -265,25 +261,16 @@ static int smsc47b397_detect(struct i2c_adapter *adapter)
|
|||
goto error_release;
|
||||
}
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
data->addr = res->start;
|
||||
data->name = "smsc47b397";
|
||||
mutex_init(&data->lock);
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &smsc47b397_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
strlcpy(new_client->name, "smsc47b397", I2C_NAME_SIZE);
|
||||
|
||||
mutex_init(&data->update_lock);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
if ((err = sysfs_create_group(&dev->kobj, &smsc47b397_group)))
|
||||
goto error_free;
|
||||
|
||||
if ((err = sysfs_create_group(&new_client->dev.kobj, &smsc47b397_group)))
|
||||
goto error_detach;
|
||||
|
||||
data->class_dev = hwmon_device_register(&new_client->dev);
|
||||
data->class_dev = hwmon_device_register(dev);
|
||||
if (IS_ERR(data->class_dev)) {
|
||||
err = PTR_ERR(data->class_dev);
|
||||
goto error_remove;
|
||||
|
@ -292,13 +279,50 @@ static int smsc47b397_detect(struct i2c_adapter *adapter)
|
|||
return 0;
|
||||
|
||||
error_remove:
|
||||
sysfs_remove_group(&new_client->dev.kobj, &smsc47b397_group);
|
||||
error_detach:
|
||||
i2c_detach_client(new_client);
|
||||
sysfs_remove_group(&dev->kobj, &smsc47b397_group);
|
||||
error_free:
|
||||
kfree(data);
|
||||
error_release:
|
||||
release_region(address, SMSC_EXTENT);
|
||||
release_region(res->start, SMSC_EXTENT);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init smsc47b397_device_add(unsigned short address)
|
||||
{
|
||||
struct resource res = {
|
||||
.start = address,
|
||||
.end = address + SMSC_EXTENT - 1,
|
||||
.name = DRVNAME,
|
||||
.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;
|
||||
}
|
||||
|
||||
|
@ -320,7 +344,7 @@ static int __init smsc47b397_find(unsigned short *addr)
|
|||
*addr = (superio_inb(SUPERIO_REG_BASE_MSB) << 8)
|
||||
| superio_inb(SUPERIO_REG_BASE_LSB);
|
||||
|
||||
printk(KERN_INFO "smsc47b397: found SMSC %s "
|
||||
printk(KERN_INFO DRVNAME ": found SMSC %s "
|
||||
"(base address 0x%04x, revision %u)\n",
|
||||
id == 0x81 ? "SCH5307-NS" : "LPC47B397-NC", *addr, rev);
|
||||
|
||||
|
@ -330,17 +354,33 @@ static int __init smsc47b397_find(unsigned short *addr)
|
|||
|
||||
static int __init smsc47b397_init(void)
|
||||
{
|
||||
unsigned short address;
|
||||
int ret;
|
||||
|
||||
if ((ret = smsc47b397_find(&address)))
|
||||
return ret;
|
||||
|
||||
return i2c_isa_add_driver(&smsc47b397_driver);
|
||||
ret = platform_driver_register(&smsc47b397_driver);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
/* Sets global pdev as a side effect */
|
||||
ret = smsc47b397_device_add(address);
|
||||
if (ret)
|
||||
goto exit_driver;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_driver:
|
||||
platform_driver_unregister(&smsc47b397_driver);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit smsc47b397_exit(void)
|
||||
{
|
||||
i2c_isa_del_driver(&smsc47b397_driver);
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&smsc47b397_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>");
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
for hardware monitoring
|
||||
|
||||
Supports the SMSC LPC47B27x, LPC47M10x, LPC47M112, LPC47M13x,
|
||||
LPC47M14x, LPC47M15x, LPC47M192 and LPC47M997 Super-I/O chips.
|
||||
LPC47M14x, LPC47M15x, LPC47M192, LPC47M292 and LPC47M997
|
||||
Super-I/O chips.
|
||||
|
||||
Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
|
||||
Copyright (C) 2004 Jean Delvare <khali@linux-fr.org>
|
||||
Copyright (C) 2004-2007 Jean Delvare <khali@linux-fr.org>
|
||||
Ported to Linux 2.6 by Gabriele Gorla <gorlik@yahoo.com>
|
||||
and Jean Delvare
|
||||
|
||||
|
@ -29,17 +30,19 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-isa.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
/* Address is autodetected, there is no default value */
|
||||
static unsigned short address;
|
||||
static struct platform_device *pdev;
|
||||
|
||||
#define DRVNAME "smsc47m1"
|
||||
enum chips { smsc47m1, smsc47m2 };
|
||||
|
||||
/* Super-I/0 registers and commands */
|
||||
|
||||
|
@ -87,10 +90,18 @@ superio_exit(void)
|
|||
#define SMSC47M1_REG_ALARM 0x04
|
||||
#define SMSC47M1_REG_TPIN(nr) (0x34 - (nr))
|
||||
#define SMSC47M1_REG_PPIN(nr) (0x36 - (nr))
|
||||
#define SMSC47M1_REG_PWM(nr) (0x56 + (nr))
|
||||
#define SMSC47M1_REG_FANDIV 0x58
|
||||
#define SMSC47M1_REG_FAN(nr) (0x59 + (nr))
|
||||
#define SMSC47M1_REG_FAN_PRELOAD(nr) (0x5B + (nr))
|
||||
|
||||
static const u8 SMSC47M1_REG_FAN[3] = { 0x59, 0x5a, 0x6b };
|
||||
static const u8 SMSC47M1_REG_FAN_PRELOAD[3] = { 0x5b, 0x5c, 0x6c };
|
||||
static const u8 SMSC47M1_REG_PWM[3] = { 0x56, 0x57, 0x69 };
|
||||
|
||||
#define SMSC47M2_REG_ALARM6 0x09
|
||||
#define SMSC47M2_REG_TPIN1 0x38
|
||||
#define SMSC47M2_REG_TPIN2 0x37
|
||||
#define SMSC47M2_REG_TPIN3 0x2d
|
||||
#define SMSC47M2_REG_PPIN3 0x2c
|
||||
#define SMSC47M2_REG_FANDIV3 0x6a
|
||||
|
||||
#define MIN_FROM_REG(reg,div) ((reg)>=192 ? 0 : \
|
||||
983040/((192-(reg))*(div)))
|
||||
|
@ -102,45 +113,57 @@ superio_exit(void)
|
|||
#define PWM_TO_REG(reg) (((reg) >> 1) & 0x7E)
|
||||
|
||||
struct smsc47m1_data {
|
||||
struct i2c_client client;
|
||||
unsigned short addr;
|
||||
const char *name;
|
||||
enum chips type;
|
||||
struct class_device *class_dev;
|
||||
struct mutex lock;
|
||||
|
||||
struct mutex update_lock;
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
||||
u8 fan[2]; /* Register value */
|
||||
u8 fan_preload[2]; /* Register value */
|
||||
u8 fan_div[2]; /* Register encoding, shifted right */
|
||||
u8 fan[3]; /* Register value */
|
||||
u8 fan_preload[3]; /* Register value */
|
||||
u8 fan_div[3]; /* Register encoding, shifted right */
|
||||
u8 alarms; /* Register encoding */
|
||||
u8 pwm[2]; /* Register value (bit 7 is enable) */
|
||||
u8 pwm[3]; /* Register value (bit 0 is disable) */
|
||||
};
|
||||
|
||||
struct smsc47m1_sio_data {
|
||||
enum chips type;
|
||||
};
|
||||
|
||||
|
||||
static int smsc47m1_detect(struct i2c_adapter *adapter);
|
||||
static int smsc47m1_detach_client(struct i2c_client *client);
|
||||
|
||||
static int smsc47m1_read_value(struct i2c_client *client, u8 reg);
|
||||
static void smsc47m1_write_value(struct i2c_client *client, u8 reg, u8 value);
|
||||
|
||||
static int smsc47m1_probe(struct platform_device *pdev);
|
||||
static int smsc47m1_remove(struct platform_device *pdev);
|
||||
static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
|
||||
int init);
|
||||
|
||||
static inline int smsc47m1_read_value(struct smsc47m1_data *data, u8 reg)
|
||||
{
|
||||
return inb_p(data->addr + reg);
|
||||
}
|
||||
|
||||
static struct i2c_driver smsc47m1_driver = {
|
||||
static inline void smsc47m1_write_value(struct smsc47m1_data *data, u8 reg,
|
||||
u8 value)
|
||||
{
|
||||
outb_p(value, data->addr + reg);
|
||||
}
|
||||
|
||||
static struct platform_driver smsc47m1_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "smsc47m1",
|
||||
.name = DRVNAME,
|
||||
},
|
||||
.attach_adapter = smsc47m1_detect,
|
||||
.detach_client = smsc47m1_detach_client,
|
||||
.probe = smsc47m1_probe,
|
||||
.remove = __devexit_p(smsc47m1_remove),
|
||||
};
|
||||
|
||||
/* nr is 0 or 1 in the callback functions below */
|
||||
|
||||
static ssize_t get_fan(struct device *dev, char *buf, int nr)
|
||||
static ssize_t get_fan(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
|
||||
int nr = attr->index;
|
||||
/* This chip (stupidly) stops monitoring fan speed if PWM is
|
||||
enabled and duty cycle is 0%. This is fine if the monitoring
|
||||
and control concern the same fan, but troublesome if they are
|
||||
|
@ -152,43 +175,54 @@ static ssize_t get_fan(struct device *dev, char *buf, int nr)
|
|||
return sprintf(buf, "%d\n", rpm);
|
||||
}
|
||||
|
||||
static ssize_t get_fan_min(struct device *dev, char *buf, int nr)
|
||||
static ssize_t get_fan_min(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
|
||||
int nr = attr->index;
|
||||
int rpm = MIN_FROM_REG(data->fan_preload[nr],
|
||||
DIV_FROM_REG(data->fan_div[nr]));
|
||||
return sprintf(buf, "%d\n", rpm);
|
||||
}
|
||||
|
||||
static ssize_t get_fan_div(struct device *dev, char *buf, int nr)
|
||||
static ssize_t get_fan_div(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
|
||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr]));
|
||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t get_pwm(struct device *dev, char *buf, int nr)
|
||||
static ssize_t get_pwm(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
|
||||
return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[nr]));
|
||||
return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t get_pwm_en(struct device *dev, char *buf, int nr)
|
||||
static ssize_t get_pwm_en(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
|
||||
return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[nr]));
|
||||
return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t get_alarms(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
static ssize_t get_alarms(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
|
||||
return sprintf(buf, "%d\n", data->alarms);
|
||||
}
|
||||
|
||||
static ssize_t set_fan_min(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
static ssize_t set_fan_min(struct device *dev, struct device_attribute
|
||||
*devattr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct smsc47m1_data *data = i2c_get_clientdata(client);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = dev_get_drvdata(dev);
|
||||
int nr = attr->index;
|
||||
long rpmdiv, val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
@ -200,7 +234,7 @@ static ssize_t set_fan_min(struct device *dev, const char *buf,
|
|||
}
|
||||
|
||||
data->fan_preload[nr] = 192 - ((983040 + rpmdiv / 2) / rpmdiv);
|
||||
smsc47m1_write_value(client, SMSC47M1_REG_FAN_PRELOAD(nr),
|
||||
smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr],
|
||||
data->fan_preload[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
|
@ -211,12 +245,12 @@ static ssize_t set_fan_min(struct device *dev, const char *buf,
|
|||
determined in part by the fan clock divider. This follows the principle
|
||||
of least surprise; the user doesn't expect the fan minimum to change just
|
||||
because the divider changed. */
|
||||
static ssize_t set_fan_div(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
static ssize_t set_fan_div(struct device *dev, struct device_attribute
|
||||
*devattr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct smsc47m1_data *data = i2c_get_clientdata(client);
|
||||
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = dev_get_drvdata(dev);
|
||||
int nr = attr->index;
|
||||
long new_div = simple_strtol(buf, NULL, 10), tmp;
|
||||
u8 old_div = DIV_FROM_REG(data->fan_div[nr]);
|
||||
|
||||
|
@ -234,27 +268,38 @@ static ssize_t set_fan_div(struct device *dev, const char *buf,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
tmp = smsc47m1_read_value(client, SMSC47M1_REG_FANDIV) & 0x0F;
|
||||
tmp |= (data->fan_div[0] << 4) | (data->fan_div[1] << 6);
|
||||
smsc47m1_write_value(client, SMSC47M1_REG_FANDIV, tmp);
|
||||
switch (nr) {
|
||||
case 0:
|
||||
case 1:
|
||||
tmp = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV)
|
||||
& ~(0x03 << (4 + 2 * nr));
|
||||
tmp |= data->fan_div[nr] << (4 + 2 * nr);
|
||||
smsc47m1_write_value(data, SMSC47M1_REG_FANDIV, tmp);
|
||||
break;
|
||||
case 2:
|
||||
tmp = smsc47m1_read_value(data, SMSC47M2_REG_FANDIV3) & 0xCF;
|
||||
tmp |= data->fan_div[2] << 4;
|
||||
smsc47m1_write_value(data, SMSC47M2_REG_FANDIV3, tmp);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Preserve fan min */
|
||||
tmp = 192 - (old_div * (192 - data->fan_preload[nr])
|
||||
+ new_div / 2) / new_div;
|
||||
data->fan_preload[nr] = SENSORS_LIMIT(tmp, 0, 191);
|
||||
smsc47m1_write_value(client, SMSC47M1_REG_FAN_PRELOAD(nr),
|
||||
smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr],
|
||||
data->fan_preload[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_pwm(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
static ssize_t set_pwm(struct device *dev, struct device_attribute
|
||||
*devattr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct smsc47m1_data *data = i2c_get_clientdata(client);
|
||||
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = dev_get_drvdata(dev);
|
||||
int nr = attr->index;
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
if (val < 0 || val > 255)
|
||||
|
@ -263,19 +308,19 @@ static ssize_t set_pwm(struct device *dev, const char *buf,
|
|||
mutex_lock(&data->update_lock);
|
||||
data->pwm[nr] &= 0x81; /* Preserve additional bits */
|
||||
data->pwm[nr] |= PWM_TO_REG(val);
|
||||
smsc47m1_write_value(client, SMSC47M1_REG_PWM(nr),
|
||||
smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr],
|
||||
data->pwm[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_pwm_en(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
static ssize_t set_pwm_en(struct device *dev, struct device_attribute
|
||||
*devattr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct smsc47m1_data *data = i2c_get_clientdata(client);
|
||||
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct smsc47m1_data *data = dev_get_drvdata(dev);
|
||||
int nr = attr->index;
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
if (val != 0 && val != 1)
|
||||
|
@ -284,7 +329,7 @@ static ssize_t set_pwm_en(struct device *dev, const char *buf,
|
|||
mutex_lock(&data->update_lock);
|
||||
data->pwm[nr] &= 0xFE; /* preserve the other bits */
|
||||
data->pwm[nr] |= !val;
|
||||
smsc47m1_write_value(client, SMSC47M1_REG_PWM(nr),
|
||||
smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr],
|
||||
data->pwm[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
|
@ -292,79 +337,55 @@ static ssize_t set_pwm_en(struct device *dev, const char *buf,
|
|||
}
|
||||
|
||||
#define fan_present(offset) \
|
||||
static ssize_t get_fan##offset (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return get_fan(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t get_fan##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return get_fan_min(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_fan##offset##_min (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_fan_min(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static ssize_t get_fan##offset##_div (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return get_fan_div(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_fan##offset##_div (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_fan_div(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static ssize_t get_pwm##offset (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return get_pwm(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_pwm##offset (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_pwm(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static ssize_t get_pwm##offset##_en (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return get_pwm_en(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_pwm##offset##_en (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_pwm_en(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static DEVICE_ATTR(fan##offset##_input, S_IRUGO, get_fan##offset, \
|
||||
NULL); \
|
||||
static DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
get_fan##offset##_min, set_fan##offset##_min); \
|
||||
static DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \
|
||||
get_fan##offset##_div, set_fan##offset##_div); \
|
||||
static DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR, \
|
||||
get_pwm##offset, set_pwm##offset); \
|
||||
static DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR, \
|
||||
get_pwm##offset##_en, set_pwm##offset##_en);
|
||||
static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, get_fan, \
|
||||
NULL, offset - 1); \
|
||||
static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
get_fan_min, set_fan_min, offset - 1); \
|
||||
static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \
|
||||
get_fan_div, set_fan_div, offset - 1); \
|
||||
static SENSOR_DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR, \
|
||||
get_pwm, set_pwm, offset - 1); \
|
||||
static SENSOR_DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR, \
|
||||
get_pwm_en, set_pwm_en, offset - 1)
|
||||
|
||||
fan_present(1);
|
||||
fan_present(2);
|
||||
fan_present(3);
|
||||
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, get_alarms, NULL);
|
||||
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct smsc47m1_data *data = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%s\n", data->name);
|
||||
}
|
||||
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
|
||||
|
||||
/* Almost all sysfs files may or may not be created depending on the chip
|
||||
setup so we create them individually. It is still convenient to define a
|
||||
group to remove them all at once. */
|
||||
static struct attribute *smsc47m1_attributes[] = {
|
||||
&dev_attr_fan1_input.attr,
|
||||
&dev_attr_fan1_min.attr,
|
||||
&dev_attr_fan1_div.attr,
|
||||
&dev_attr_fan2_input.attr,
|
||||
&dev_attr_fan2_min.attr,
|
||||
&dev_attr_fan2_div.attr,
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_div.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_div.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_div.dev_attr.attr,
|
||||
|
||||
&dev_attr_pwm1.attr,
|
||||
&dev_attr_pwm1_enable.attr,
|
||||
&dev_attr_pwm2.attr,
|
||||
&dev_attr_pwm2_enable.attr,
|
||||
&sensor_dev_attr_pwm1.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm2.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm2_enable.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_enable.dev_attr.attr,
|
||||
|
||||
&dev_attr_alarms.attr,
|
||||
&dev_attr_name.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -372,7 +393,8 @@ static const struct attribute_group smsc47m1_group = {
|
|||
.attrs = smsc47m1_attributes,
|
||||
};
|
||||
|
||||
static int __init smsc47m1_find(unsigned short *addr)
|
||||
static int __init smsc47m1_find(unsigned short *addr,
|
||||
struct smsc47m1_sio_data *sio_data)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
|
@ -386,18 +408,32 @@ static int __init smsc47m1_find(unsigned short *addr)
|
|||
* can do much more besides (device id 0x60).
|
||||
* The LPC47M997 is undocumented, but seems to be compatible with
|
||||
* the LPC47M192, and has the same device id.
|
||||
* The LPC47M292 (device id 0x6B) is somewhat compatible, but it
|
||||
* supports a 3rd fan, and the pin configuration registers are
|
||||
* unfortunately different.
|
||||
*/
|
||||
if (val == 0x51)
|
||||
printk(KERN_INFO "smsc47m1: Found SMSC LPC47B27x\n");
|
||||
else if (val == 0x59)
|
||||
printk(KERN_INFO "smsc47m1: Found SMSC "
|
||||
"LPC47M10x/LPC47M112/LPC47M13x\n");
|
||||
else if (val == 0x5F)
|
||||
printk(KERN_INFO "smsc47m1: Found SMSC LPC47M14x\n");
|
||||
else if (val == 0x60)
|
||||
printk(KERN_INFO "smsc47m1: Found SMSC "
|
||||
"LPC47M15x/LPC47M192/LPC47M997\n");
|
||||
else {
|
||||
switch (val) {
|
||||
case 0x51:
|
||||
pr_info(DRVNAME ": Found SMSC LPC47B27x\n");
|
||||
sio_data->type = smsc47m1;
|
||||
break;
|
||||
case 0x59:
|
||||
pr_info(DRVNAME ": Found SMSC LPC47M10x/LPC47M112/LPC47M13x\n");
|
||||
sio_data->type = smsc47m1;
|
||||
break;
|
||||
case 0x5F:
|
||||
pr_info(DRVNAME ": Found SMSC LPC47M14x\n");
|
||||
sio_data->type = smsc47m1;
|
||||
break;
|
||||
case 0x60:
|
||||
pr_info(DRVNAME ": Found SMSC LPC47M15x/LPC47M192/LPC47M997\n");
|
||||
sio_data->type = smsc47m1;
|
||||
break;
|
||||
case 0x6B:
|
||||
pr_info(DRVNAME ": Found SMSC LPC47M292\n");
|
||||
sio_data->type = smsc47m2;
|
||||
break;
|
||||
default:
|
||||
superio_exit();
|
||||
return -ENODEV;
|
||||
}
|
||||
|
@ -407,7 +443,7 @@ static int __init smsc47m1_find(unsigned short *addr)
|
|||
| superio_inb(SUPERIO_REG_BASE + 1);
|
||||
val = superio_inb(SUPERIO_REG_ACT);
|
||||
if (*addr == 0 || (val & 0x01) == 0) {
|
||||
printk(KERN_INFO "smsc47m1: Device is disabled, will not use\n");
|
||||
pr_info(DRVNAME ": Device is disabled, will not use\n");
|
||||
superio_exit();
|
||||
return -ENODEV;
|
||||
}
|
||||
|
@ -416,15 +452,25 @@ static int __init smsc47m1_find(unsigned short *addr)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int smsc47m1_detect(struct i2c_adapter *adapter)
|
||||
static int __devinit smsc47m1_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct smsc47m1_sio_data *sio_data = dev->platform_data;
|
||||
struct smsc47m1_data *data;
|
||||
struct resource *res;
|
||||
int err = 0;
|
||||
int fan1, fan2, pwm1, pwm2;
|
||||
int fan1, fan2, fan3, pwm1, pwm2, pwm3;
|
||||
|
||||
if (!request_region(address, SMSC_EXTENT, smsc47m1_driver.driver.name)) {
|
||||
dev_err(&adapter->dev, "Region 0x%x already in use!\n", address);
|
||||
static const char *names[] = {
|
||||
"smsc47m1",
|
||||
"smsc47m2",
|
||||
};
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!request_region(res->start, SMSC_EXTENT, DRVNAME)) {
|
||||
dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
|
||||
(unsigned long)res->start,
|
||||
(unsigned long)res->end);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
|
@ -433,93 +479,114 @@ static int smsc47m1_detect(struct i2c_adapter *adapter)
|
|||
goto error_release;
|
||||
}
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
mutex_init(&data->lock);
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &smsc47m1_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
strlcpy(new_client->name, "smsc47m1", I2C_NAME_SIZE);
|
||||
data->addr = res->start;
|
||||
data->type = sio_data->type;
|
||||
data->name = names[sio_data->type];
|
||||
mutex_init(&data->update_lock);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
/* If no function is properly configured, there's no point in
|
||||
actually registering the chip. */
|
||||
fan1 = (smsc47m1_read_value(new_client, SMSC47M1_REG_TPIN(0)) & 0x05)
|
||||
== 0x05;
|
||||
fan2 = (smsc47m1_read_value(new_client, SMSC47M1_REG_TPIN(1)) & 0x05)
|
||||
== 0x05;
|
||||
pwm1 = (smsc47m1_read_value(new_client, SMSC47M1_REG_PPIN(0)) & 0x05)
|
||||
pwm1 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(0)) & 0x05)
|
||||
== 0x04;
|
||||
pwm2 = (smsc47m1_read_value(new_client, SMSC47M1_REG_PPIN(1)) & 0x05)
|
||||
pwm2 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(1)) & 0x05)
|
||||
== 0x04;
|
||||
if (!(fan1 || fan2 || pwm1 || pwm2)) {
|
||||
dev_warn(&adapter->dev, "Device at 0x%x is not configured, "
|
||||
"will not use\n", new_client->addr);
|
||||
if (data->type == smsc47m2) {
|
||||
fan1 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN1)
|
||||
& 0x0d) == 0x09;
|
||||
fan2 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN2)
|
||||
& 0x0d) == 0x09;
|
||||
fan3 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN3)
|
||||
& 0x0d) == 0x0d;
|
||||
pwm3 = (smsc47m1_read_value(data, SMSC47M2_REG_PPIN3)
|
||||
& 0x0d) == 0x08;
|
||||
} else {
|
||||
fan1 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(0))
|
||||
& 0x05) == 0x05;
|
||||
fan2 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(1))
|
||||
& 0x05) == 0x05;
|
||||
fan3 = 0;
|
||||
pwm3 = 0;
|
||||
}
|
||||
if (!(fan1 || fan2 || fan3 || pwm1 || pwm2 || pwm3)) {
|
||||
dev_warn(dev, "Device not configured, will not use\n");
|
||||
err = -ENODEV;
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto error_free;
|
||||
|
||||
/* Some values (fan min, clock dividers, pwm registers) may be
|
||||
needed before any update is triggered, so we better read them
|
||||
at least once here. We don't usually do it that way, but in
|
||||
this particular case, manually reading 5 registers out of 8
|
||||
doesn't make much sense and we're better using the existing
|
||||
function. */
|
||||
smsc47m1_update_device(&new_client->dev, 1);
|
||||
smsc47m1_update_device(dev, 1);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
if (fan1) {
|
||||
if ((err = device_create_file(&new_client->dev,
|
||||
&dev_attr_fan1_input))
|
||||
|| (err = device_create_file(&new_client->dev,
|
||||
&dev_attr_fan1_min))
|
||||
|| (err = device_create_file(&new_client->dev,
|
||||
&dev_attr_fan1_div)))
|
||||
if ((err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan1_input.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan1_min.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan1_div.dev_attr)))
|
||||
goto error_remove_files;
|
||||
} else
|
||||
dev_dbg(&new_client->dev, "Fan 1 not enabled by hardware, "
|
||||
"skipping\n");
|
||||
dev_dbg(dev, "Fan 1 not enabled by hardware, skipping\n");
|
||||
|
||||
if (fan2) {
|
||||
if ((err = device_create_file(&new_client->dev,
|
||||
&dev_attr_fan2_input))
|
||||
|| (err = device_create_file(&new_client->dev,
|
||||
&dev_attr_fan2_min))
|
||||
|| (err = device_create_file(&new_client->dev,
|
||||
&dev_attr_fan2_div)))
|
||||
if ((err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan2_input.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan2_min.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan2_div.dev_attr)))
|
||||
goto error_remove_files;
|
||||
} else
|
||||
dev_dbg(&new_client->dev, "Fan 2 not enabled by hardware, "
|
||||
"skipping\n");
|
||||
dev_dbg(dev, "Fan 2 not enabled by hardware, skipping\n");
|
||||
|
||||
if (fan3) {
|
||||
if ((err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan3_input.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan3_min.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_fan3_div.dev_attr)))
|
||||
goto error_remove_files;
|
||||
} else
|
||||
dev_dbg(dev, "Fan 3 not enabled by hardware, skipping\n");
|
||||
|
||||
if (pwm1) {
|
||||
if ((err = device_create_file(&new_client->dev,
|
||||
&dev_attr_pwm1))
|
||||
|| (err = device_create_file(&new_client->dev,
|
||||
&dev_attr_pwm1_enable)))
|
||||
if ((err = device_create_file(dev,
|
||||
&sensor_dev_attr_pwm1.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_pwm1_enable.dev_attr)))
|
||||
goto error_remove_files;
|
||||
} else
|
||||
dev_dbg(&new_client->dev, "PWM 1 not enabled by hardware, "
|
||||
"skipping\n");
|
||||
if (pwm2) {
|
||||
if ((err = device_create_file(&new_client->dev,
|
||||
&dev_attr_pwm2))
|
||||
|| (err = device_create_file(&new_client->dev,
|
||||
&dev_attr_pwm2_enable)))
|
||||
goto error_remove_files;
|
||||
} else
|
||||
dev_dbg(&new_client->dev, "PWM 2 not enabled by hardware, "
|
||||
"skipping\n");
|
||||
dev_dbg(dev, "PWM 1 not enabled by hardware, skipping\n");
|
||||
|
||||
if ((err = device_create_file(&new_client->dev, &dev_attr_alarms)))
|
||||
if (pwm2) {
|
||||
if ((err = device_create_file(dev,
|
||||
&sensor_dev_attr_pwm2.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_pwm2_enable.dev_attr)))
|
||||
goto error_remove_files;
|
||||
} else
|
||||
dev_dbg(dev, "PWM 2 not enabled by hardware, skipping\n");
|
||||
|
||||
if (pwm3) {
|
||||
if ((err = device_create_file(dev,
|
||||
&sensor_dev_attr_pwm3.dev_attr))
|
||||
|| (err = device_create_file(dev,
|
||||
&sensor_dev_attr_pwm3_enable.dev_attr)))
|
||||
goto error_remove_files;
|
||||
} else
|
||||
dev_dbg(dev, "PWM 3 not enabled by hardware, skipping\n");
|
||||
|
||||
if ((err = device_create_file(dev, &dev_attr_alarms)))
|
||||
goto error_remove_files;
|
||||
|
||||
data->class_dev = hwmon_device_register(&new_client->dev);
|
||||
data->class_dev = hwmon_device_register(dev);
|
||||
if (IS_ERR(data->class_dev)) {
|
||||
err = PTR_ERR(data->class_dev);
|
||||
goto error_remove_files;
|
||||
|
@ -528,78 +595,71 @@ static int smsc47m1_detect(struct i2c_adapter *adapter)
|
|||
return 0;
|
||||
|
||||
error_remove_files:
|
||||
sysfs_remove_group(&new_client->dev.kobj, &smsc47m1_group);
|
||||
i2c_detach_client(new_client);
|
||||
sysfs_remove_group(&dev->kobj, &smsc47m1_group);
|
||||
error_free:
|
||||
kfree(data);
|
||||
error_release:
|
||||
release_region(address, SMSC_EXTENT);
|
||||
release_region(res->start, SMSC_EXTENT);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int smsc47m1_detach_client(struct i2c_client *client)
|
||||
static int __devexit smsc47m1_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct smsc47m1_data *data = i2c_get_clientdata(client);
|
||||
int err;
|
||||
struct smsc47m1_data *data = platform_get_drvdata(pdev);
|
||||
struct resource *res;
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
hwmon_device_unregister(data->class_dev);
|
||||
sysfs_remove_group(&client->dev.kobj, &smsc47m1_group);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &smsc47m1_group);
|
||||
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
release_region(client->addr, SMSC_EXTENT);
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_region(res->start, SMSC_EXTENT);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smsc47m1_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
int res;
|
||||
|
||||
mutex_lock(&((struct smsc47m1_data *) i2c_get_clientdata(client))->lock);
|
||||
res = inb_p(client->addr + reg);
|
||||
mutex_unlock(&((struct smsc47m1_data *) i2c_get_clientdata(client))->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void smsc47m1_write_value(struct i2c_client *client, u8 reg, u8 value)
|
||||
{
|
||||
mutex_lock(&((struct smsc47m1_data *) i2c_get_clientdata(client))->lock);
|
||||
outb_p(value, client->addr + reg);
|
||||
mutex_unlock(&((struct smsc47m1_data *) i2c_get_clientdata(client))->lock);
|
||||
}
|
||||
|
||||
static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
|
||||
int init)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct smsc47m1_data *data = i2c_get_clientdata(client);
|
||||
struct smsc47m1_data *data = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || init) {
|
||||
int i;
|
||||
int i, fan_nr;
|
||||
fan_nr = data->type == smsc47m2 ? 3 : 2;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
data->fan[i] = smsc47m1_read_value(client,
|
||||
SMSC47M1_REG_FAN(i));
|
||||
data->fan_preload[i] = smsc47m1_read_value(client,
|
||||
SMSC47M1_REG_FAN_PRELOAD(i));
|
||||
data->pwm[i] = smsc47m1_read_value(client,
|
||||
SMSC47M1_REG_PWM(i));
|
||||
for (i = 0; i < fan_nr; i++) {
|
||||
data->fan[i] = smsc47m1_read_value(data,
|
||||
SMSC47M1_REG_FAN[i]);
|
||||
data->fan_preload[i] = smsc47m1_read_value(data,
|
||||
SMSC47M1_REG_FAN_PRELOAD[i]);
|
||||
data->pwm[i] = smsc47m1_read_value(data,
|
||||
SMSC47M1_REG_PWM[i]);
|
||||
}
|
||||
|
||||
i = smsc47m1_read_value(client, SMSC47M1_REG_FANDIV);
|
||||
i = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV);
|
||||
data->fan_div[0] = (i >> 4) & 0x03;
|
||||
data->fan_div[1] = i >> 6;
|
||||
|
||||
data->alarms = smsc47m1_read_value(client,
|
||||
data->alarms = smsc47m1_read_value(data,
|
||||
SMSC47M1_REG_ALARM) >> 6;
|
||||
/* Clear alarms if needed */
|
||||
if (data->alarms)
|
||||
smsc47m1_write_value(client, SMSC47M1_REG_ALARM, 0xC0);
|
||||
smsc47m1_write_value(data, SMSC47M1_REG_ALARM, 0xC0);
|
||||
|
||||
if (fan_nr >= 3) {
|
||||
data->fan_div[2] = (smsc47m1_read_value(data,
|
||||
SMSC47M2_REG_FANDIV3) >> 4) & 0x03;
|
||||
data->alarms |= (smsc47m1_read_value(data,
|
||||
SMSC47M2_REG_ALARM6) & 0x40) >> 4;
|
||||
/* Clear alarm if needed */
|
||||
if (data->alarms & 0x04)
|
||||
smsc47m1_write_value(data,
|
||||
SMSC47M2_REG_ALARM6,
|
||||
0x40);
|
||||
}
|
||||
|
||||
data->last_updated = jiffies;
|
||||
}
|
||||
|
@ -608,18 +668,86 @@ static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
|
|||
return data;
|
||||
}
|
||||
|
||||
static int __init sm_smsc47m1_init(void)
|
||||
static int __init smsc47m1_device_add(unsigned short address,
|
||||
const struct smsc47m1_sio_data *sio_data)
|
||||
{
|
||||
if (smsc47m1_find(&address)) {
|
||||
return -ENODEV;
|
||||
struct resource res = {
|
||||
.start = address,
|
||||
.end = address + SMSC_EXTENT - 1,
|
||||
.name = DRVNAME,
|
||||
.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;
|
||||
}
|
||||
|
||||
return i2c_isa_add_driver(&smsc47m1_driver);
|
||||
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;
|
||||
}
|
||||
|
||||
pdev->dev.platform_data = kmalloc(sizeof(struct smsc47m1_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 smsc47m1_sio_data));
|
||||
|
||||
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 sm_smsc47m1_init(void)
|
||||
{
|
||||
int err;
|
||||
unsigned short address;
|
||||
struct smsc47m1_sio_data sio_data;
|
||||
|
||||
if (smsc47m1_find(&address, &sio_data))
|
||||
return -ENODEV;
|
||||
|
||||
err = platform_driver_register(&smsc47m1_driver);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
/* Sets global pdev as a side effect */
|
||||
err = smsc47m1_device_add(address, &sio_data);
|
||||
if (err)
|
||||
goto exit_driver;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_driver:
|
||||
platform_driver_unregister(&smsc47m1_driver);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit sm_smsc47m1_exit(void)
|
||||
{
|
||||
i2c_isa_del_driver(&smsc47m1_driver);
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&smsc47m1_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
smsc47m192.c - Support for hardware monitoring block of
|
||||
SMSC LPC47M192 and LPC47M997 Super I/O chips
|
||||
SMSC LPC47M192 and compatible Super I/O chips
|
||||
|
||||
Copyright (C) 2006 Hartmut Rick <linux@rick.claranet.de>
|
||||
|
||||
|
@ -518,7 +518,7 @@ static int smsc47m192_detect(struct i2c_adapter *adapter, int address,
|
|||
&& (i2c_smbus_read_byte_data(client,
|
||||
SMSC47M192_REG_VID4) & 0xfe) == 0x80) {
|
||||
dev_info(&adapter->dev,
|
||||
"found SMSC47M192 or SMSC47M997, "
|
||||
"found SMSC47M192 or compatible, "
|
||||
"version 2, stepping A%d\n", version & 0x0f);
|
||||
} else {
|
||||
dev_dbg(&adapter->dev,
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <linux/hwmon-vid.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
static int uch_config = -1;
|
||||
|
@ -1130,6 +1131,12 @@ static int __devinit vt1211_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!request_region(res->start, res->end - res->start + 1, DRVNAME)) {
|
||||
err = -EBUSY;
|
||||
dev_err(dev, "Failed to request region 0x%lx-0x%lx\n",
|
||||
(unsigned long)res->start, (unsigned long)res->end);
|
||||
goto EXIT_KFREE;
|
||||
}
|
||||
data->addr = res->start;
|
||||
data->name = DRVNAME;
|
||||
mutex_init(&data->update_lock);
|
||||
|
@ -1197,6 +1204,8 @@ EXIT_DEV_REMOVE:
|
|||
dev_err(dev, "Sysfs interface creation failed (%d)\n", err);
|
||||
EXIT_DEV_REMOVE_SILENT:
|
||||
vt1211_remove_sysfs(pdev);
|
||||
release_region(res->start, res->end - res->start + 1);
|
||||
EXIT_KFREE:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
EXIT:
|
||||
|
@ -1206,12 +1215,16 @@ EXIT:
|
|||
static int __devexit vt1211_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct vt1211_data *data = platform_get_drvdata(pdev);
|
||||
struct resource *res;
|
||||
|
||||
hwmon_device_unregister(data->class_dev);
|
||||
vt1211_remove_sysfs(pdev);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_region(res->start, res->end - res->start + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -77,7 +77,7 @@ static inline unsigned long long native_read_pmc(void)
|
|||
#ifdef CONFIG_PARAVIRT
|
||||
#include <asm/paravirt.h>
|
||||
#else
|
||||
|
||||
#include <linux/errno.h>
|
||||
/*
|
||||
* Access to machine-specific registers (available on 586 and better only)
|
||||
* Note: the rd* operations modify the parameters directly (without using
|
||||
|
@ -148,6 +148,8 @@ static inline void wrmsrl (unsigned long msr, unsigned long long val)
|
|||
#ifdef CONFIG_SMP
|
||||
void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h);
|
||||
void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h);
|
||||
int rdmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h);
|
||||
int wrmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h);
|
||||
#else /* CONFIG_SMP */
|
||||
static inline void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
|
||||
{
|
||||
|
@ -157,6 +159,14 @@ static inline void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
|
|||
{
|
||||
wrmsr(msr_no, l, h);
|
||||
}
|
||||
static inline int rdmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
|
||||
{
|
||||
return rdmsr_safe(msr_no, l, h);
|
||||
}
|
||||
static inline int wrmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
|
||||
{
|
||||
return wrmsr_safe(msr_no, l, h);
|
||||
}
|
||||
#endif /* CONFIG_SMP */
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <asm/msr-index.h>
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
#include <linux/errno.h>
|
||||
/*
|
||||
* Access to machine-specific registers (available on 586 and better only)
|
||||
* Note: the rd* operations modify the parameters directly (without using
|
||||
|
@ -162,6 +163,8 @@ static inline unsigned int cpuid_edx(unsigned int op)
|
|||
#ifdef CONFIG_SMP
|
||||
void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h);
|
||||
void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h);
|
||||
int rdmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h);
|
||||
int wrmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h);
|
||||
#else /* CONFIG_SMP */
|
||||
static inline void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
|
||||
{
|
||||
|
@ -171,6 +174,14 @@ static inline void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
|
|||
{
|
||||
wrmsr(msr_no, l, h);
|
||||
}
|
||||
static inline int rdmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
|
||||
{
|
||||
return rdmsr_safe(msr_no, l, h);
|
||||
}
|
||||
static inline int wrmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
|
||||
{
|
||||
return wrmsr_safe(msr_no, l, h);
|
||||
}
|
||||
#endif /* CONFIG_SMP */
|
||||
#endif /* __ASSEMBLY__ */
|
||||
#endif /* X86_64_MSR_H */
|
||||
|
|
Loading…
Reference in New Issue