hwmon/smsc47m1: Add support for the LPC47M292

The new SMSC LPC47M292 Super-I/O chip is a bit different from the
previous ones, it supports a 3rd fan, but unfortunately the pin
configuration registers are different.

Signed-off-by: Jean Delvare <khali@linux-fr.org>
This commit is contained in:
Jean Delvare 2007-05-08 17:21:59 +02:00 committed by Jean Delvare
parent ce7ee4e80a
commit 8eccbb6fb9
3 changed files with 139 additions and 41 deletions

View file

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

View file

@ -422,11 +422,12 @@ config SENSORS_SMSC47M1
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.

View file

@ -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
@ -40,6 +41,8 @@
/* Address is autodetected, there is no default value */
static unsigned short address;
static u8 devid;
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)))
@ -103,17 +114,18 @@ superio_exit(void)
struct smsc47m1_data {
struct i2c_client client;
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) */
};
@ -200,7 +212,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(client, SMSC47M1_REG_FAN_PRELOAD[nr],
data->fan_preload[nr]);
mutex_unlock(&data->update_lock);
@ -234,15 +246,26 @@ 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(client, SMSC47M1_REG_FANDIV)
& ~(0x03 << (4 + 2 * nr));
tmp |= data->fan_div[nr] << (4 + 2 * nr);
smsc47m1_write_value(client, SMSC47M1_REG_FANDIV, tmp);
break;
case 2:
tmp = smsc47m1_read_value(client, SMSC47M2_REG_FANDIV3) & 0xCF;
tmp |= data->fan_div[2] << 4;
smsc47m1_write_value(client, 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(client, SMSC47M1_REG_FAN_PRELOAD[nr],
data->fan_preload[nr]);
mutex_unlock(&data->update_lock);
@ -263,7 +286,7 @@ 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(client, SMSC47M1_REG_PWM[nr],
data->pwm[nr]);
mutex_unlock(&data->update_lock);
@ -284,7 +307,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(client, SMSC47M1_REG_PWM[nr],
data->pwm[nr]);
mutex_unlock(&data->update_lock);
@ -345,6 +368,7 @@ static DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR, \
fan_present(1);
fan_present(2);
fan_present(3);
static DEVICE_ATTR(alarms, S_IRUGO, get_alarms, NULL);
@ -358,11 +382,16 @@ static struct attribute *smsc47m1_attributes[] = {
&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,
&dev_attr_pwm1.attr,
&dev_attr_pwm1_enable.attr,
&dev_attr_pwm2.attr,
&dev_attr_pwm2_enable.attr,
&dev_attr_pwm3.attr,
&dev_attr_pwm3_enable.attr,
&dev_attr_alarms.attr,
NULL
@ -377,7 +406,7 @@ static int __init smsc47m1_find(unsigned short *addr)
u8 val;
superio_enter();
val = superio_inb(SUPERIO_REG_DEVID);
devid = superio_inb(SUPERIO_REG_DEVID);
/*
* SMSC LPC47M10x/LPC47M112/LPC47M13x (device id 0x59), LPC47M14x
@ -386,18 +415,29 @@ 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)
switch (devid) {
case 0x51:
printk(KERN_INFO "smsc47m1: Found SMSC LPC47B27x\n");
else if (val == 0x59)
break;
case 0x59:
printk(KERN_INFO "smsc47m1: Found SMSC "
"LPC47M10x/LPC47M112/LPC47M13x\n");
else if (val == 0x5F)
break;
case 0x5F:
printk(KERN_INFO "smsc47m1: Found SMSC LPC47M14x\n");
else if (val == 0x60)
break;
case 0x60:
printk(KERN_INFO "smsc47m1: Found SMSC "
"LPC47M15x/LPC47M192/LPC47M997\n");
else {
break;
case 0x6B:
printk(KERN_INFO "smsc47m1: Found SMSC LPC47M292\n");
break;
default:
superio_exit();
return -ENODEV;
}
@ -421,7 +461,7 @@ static int smsc47m1_detect(struct i2c_adapter *adapter)
struct i2c_client *new_client;
struct smsc47m1_data *data;
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);
@ -433,6 +473,7 @@ static int smsc47m1_detect(struct i2c_adapter *adapter)
goto error_release;
}
data->type = devid == 0x6B ? smsc47m2 : smsc47m1;
new_client = &data->client;
i2c_set_clientdata(new_client, data);
new_client->addr = address;
@ -441,20 +482,35 @@ static int smsc47m1_detect(struct i2c_adapter *adapter)
new_client->driver = &smsc47m1_driver;
new_client->flags = 0;
strlcpy(new_client->name, "smsc47m1", I2C_NAME_SIZE);
strlcpy(new_client->name,
data->type == smsc47m2 ? "smsc47m2" : "smsc47m1",
I2C_NAME_SIZE);
mutex_init(&data->update_lock);
/* 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)
== 0x04;
pwm2 = (smsc47m1_read_value(new_client, SMSC47M1_REG_PPIN(1)) & 0x05)
== 0x04;
if (!(fan1 || fan2 || pwm1 || pwm2)) {
if (data->type == smsc47m2) {
fan1 = (smsc47m1_read_value(new_client, SMSC47M2_REG_TPIN1)
& 0x0d) == 0x09;
fan2 = (smsc47m1_read_value(new_client, SMSC47M2_REG_TPIN2)
& 0x0d) == 0x09;
fan3 = (smsc47m1_read_value(new_client, SMSC47M2_REG_TPIN3)
& 0x0d) == 0x0d;
pwm3 = (smsc47m1_read_value(new_client, SMSC47M2_REG_PPIN3)
& 0x0d) == 0x08;
} else {
fan1 = (smsc47m1_read_value(new_client, SMSC47M1_REG_TPIN(0))
& 0x05) == 0x05;
fan2 = (smsc47m1_read_value(new_client, SMSC47M1_REG_TPIN(1))
& 0x05) == 0x05;
fan3 = 0;
pwm3 = 0;
}
if (!(fan1 || fan2 || fan3 || pwm1 || pwm2 || pwm3)) {
dev_warn(&adapter->dev, "Device at 0x%x is not configured, "
"will not use\n", new_client->addr);
err = -ENODEV;
@ -497,6 +553,18 @@ static int smsc47m1_detect(struct i2c_adapter *adapter)
dev_dbg(&new_client->dev, "Fan 2 not enabled by hardware, "
"skipping\n");
if (fan3) {
if ((err = device_create_file(&new_client->dev,
&dev_attr_fan3_input))
|| (err = device_create_file(&new_client->dev,
&dev_attr_fan3_min))
|| (err = device_create_file(&new_client->dev,
&dev_attr_fan3_div)))
goto error_remove_files;
} else
dev_dbg(&new_client->dev, "Fan 3 not enabled by hardware, "
"skipping\n");
if (pwm1) {
if ((err = device_create_file(&new_client->dev,
&dev_attr_pwm1))
@ -506,6 +574,7 @@ static int smsc47m1_detect(struct i2c_adapter *adapter)
} 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))
@ -516,6 +585,16 @@ static int smsc47m1_detect(struct i2c_adapter *adapter)
dev_dbg(&new_client->dev, "PWM 2 not enabled by hardware, "
"skipping\n");
if (pwm3) {
if ((err = device_create_file(&new_client->dev,
&dev_attr_pwm3))
|| (err = device_create_file(&new_client->dev,
&dev_attr_pwm3_enable)))
goto error_remove_files;
} else
dev_dbg(&new_client->dev, "PWM 3 not enabled by hardware, "
"skipping\n");
if ((err = device_create_file(&new_client->dev, &dev_attr_alarms)))
goto error_remove_files;
@ -580,15 +659,16 @@ static struct smsc47m1_data *smsc47m1_update_device(struct device *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++) {
for (i = 0; i < fan_nr; i++) {
data->fan[i] = smsc47m1_read_value(client,
SMSC47M1_REG_FAN(i));
SMSC47M1_REG_FAN[i]);
data->fan_preload[i] = smsc47m1_read_value(client,
SMSC47M1_REG_FAN_PRELOAD(i));
SMSC47M1_REG_FAN_PRELOAD[i]);
data->pwm[i] = smsc47m1_read_value(client,
SMSC47M1_REG_PWM(i));
SMSC47M1_REG_PWM[i]);
}
i = smsc47m1_read_value(client, SMSC47M1_REG_FANDIV);
@ -601,6 +681,18 @@ static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
if (data->alarms)
smsc47m1_write_value(client, SMSC47M1_REG_ALARM, 0xC0);
if (fan_nr >= 3) {
data->fan_div[2] = (smsc47m1_read_value(client,
SMSC47M2_REG_FANDIV3) >> 4) & 0x03;
data->alarms |= (smsc47m1_read_value(client,
SMSC47M2_REG_ALARM6) & 0x40) >> 4;
/* Clear alarm if needed */
if (data->alarms & 0x04)
smsc47m1_write_value(client,
SMSC47M2_REG_ALARM6,
0x40);
}
data->last_updated = jiffies;
}