mirror of
https://github.com/a52xq-dev/kernel.git
synced 2026-10-19 06:08:12 +07:00
[PATCH] I2C: Move hwmon drivers (2/3)
Part 2: Move the driver files themselves. Note that the patch "adds trailing whitespace", because it does move the files as-is, and some files happen to have trailing whitespace. From: Jean Delvare <khali@linux-fr.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
ad2f931dcb
commit
8d5d45fb14
34 changed files with 0 additions and 0 deletions
402
drivers/hwmon/adm1021.c
Normal file
402
drivers/hwmon/adm1021.c
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
adm1021.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and
|
||||
Philip Edelbrock <phil@netroedge.com>
|
||||
|
||||
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/i2c-sensor.h>
|
||||
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x18, 0x19, 0x1a,
|
||||
0x29, 0x2a, 0x2b,
|
||||
0x4c, 0x4d, 0x4e,
|
||||
I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_8(adm1021, adm1023, max1617, max1617a, thmc10, lm84, gl523sm, mc1066);
|
||||
|
||||
/* adm1021 constants specified below */
|
||||
|
||||
/* The adm1021 registers */
|
||||
/* Read-only */
|
||||
#define ADM1021_REG_TEMP 0x00
|
||||
#define ADM1021_REG_REMOTE_TEMP 0x01
|
||||
#define ADM1021_REG_STATUS 0x02
|
||||
#define ADM1021_REG_MAN_ID 0x0FE /* 0x41 = AMD, 0x49 = TI, 0x4D = Maxim, 0x23 = Genesys , 0x54 = Onsemi*/
|
||||
#define ADM1021_REG_DEV_ID 0x0FF /* ADM1021 = 0x0X, ADM1023 = 0x3X */
|
||||
#define ADM1021_REG_DIE_CODE 0x0FF /* MAX1617A */
|
||||
/* These use different addresses for reading/writing */
|
||||
#define ADM1021_REG_CONFIG_R 0x03
|
||||
#define ADM1021_REG_CONFIG_W 0x09
|
||||
#define ADM1021_REG_CONV_RATE_R 0x04
|
||||
#define ADM1021_REG_CONV_RATE_W 0x0A
|
||||
/* These are for the ADM1023's additional precision on the remote temp sensor */
|
||||
#define ADM1021_REG_REM_TEMP_PREC 0x010
|
||||
#define ADM1021_REG_REM_OFFSET 0x011
|
||||
#define ADM1021_REG_REM_OFFSET_PREC 0x012
|
||||
#define ADM1021_REG_REM_TOS_PREC 0x013
|
||||
#define ADM1021_REG_REM_THYST_PREC 0x014
|
||||
/* limits */
|
||||
#define ADM1021_REG_TOS_R 0x05
|
||||
#define ADM1021_REG_TOS_W 0x0B
|
||||
#define ADM1021_REG_REMOTE_TOS_R 0x07
|
||||
#define ADM1021_REG_REMOTE_TOS_W 0x0D
|
||||
#define ADM1021_REG_THYST_R 0x06
|
||||
#define ADM1021_REG_THYST_W 0x0C
|
||||
#define ADM1021_REG_REMOTE_THYST_R 0x08
|
||||
#define ADM1021_REG_REMOTE_THYST_W 0x0E
|
||||
/* write-only */
|
||||
#define ADM1021_REG_ONESHOT 0x0F
|
||||
|
||||
|
||||
/* Conversions. Rounding and limit checking is only done on the TO_REG
|
||||
variants. Note that you should be a bit careful with which arguments
|
||||
these macros are called: arguments may be evaluated more than once.
|
||||
Fixing this is just not worth it. */
|
||||
/* Conversions note: 1021 uses normal integer signed-byte format*/
|
||||
#define TEMP_FROM_REG(val) (val > 127 ? (val-256)*1000 : val*1000)
|
||||
#define TEMP_TO_REG(val) (SENSORS_LIMIT((val < 0 ? (val/1000)+256 : val/1000),0,255))
|
||||
|
||||
/* Initial values */
|
||||
|
||||
/* Note: Even though I left the low and high limits named os and hyst,
|
||||
they don't quite work like a thermostat the way the LM75 does. I.e.,
|
||||
a lower temp than THYST actually triggers an alarm instead of
|
||||
clearing it. Weird, ey? --Phil */
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct adm1021_data {
|
||||
struct i2c_client client;
|
||||
enum chips type;
|
||||
|
||||
struct semaphore update_lock;
|
||||
char valid; /* !=0 if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
||||
u8 temp_max; /* Register values */
|
||||
u8 temp_hyst;
|
||||
u8 temp_input;
|
||||
u8 remote_temp_max;
|
||||
u8 remote_temp_hyst;
|
||||
u8 remote_temp_input;
|
||||
u8 alarms;
|
||||
/* Special values for ADM1023 only */
|
||||
u8 remote_temp_prec;
|
||||
u8 remote_temp_os_prec;
|
||||
u8 remote_temp_hyst_prec;
|
||||
u8 remote_temp_offset;
|
||||
u8 remote_temp_offset_prec;
|
||||
};
|
||||
|
||||
static int adm1021_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int adm1021_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void adm1021_init_client(struct i2c_client *client);
|
||||
static int adm1021_detach_client(struct i2c_client *client);
|
||||
static int adm1021_read_value(struct i2c_client *client, u8 reg);
|
||||
static int adm1021_write_value(struct i2c_client *client, u8 reg,
|
||||
u16 value);
|
||||
static struct adm1021_data *adm1021_update_device(struct device *dev);
|
||||
|
||||
/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */
|
||||
static int read_only = 0;
|
||||
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver adm1021_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "adm1021",
|
||||
.id = I2C_DRIVERID_ADM1021,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = adm1021_attach_adapter,
|
||||
.detach_client = adm1021_detach_client,
|
||||
};
|
||||
|
||||
#define show(value) \
|
||||
static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct adm1021_data *data = adm1021_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->value)); \
|
||||
}
|
||||
show(temp_max);
|
||||
show(temp_hyst);
|
||||
show(temp_input);
|
||||
show(remote_temp_max);
|
||||
show(remote_temp_hyst);
|
||||
show(remote_temp_input);
|
||||
|
||||
#define show2(value) \
|
||||
static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct adm1021_data *data = adm1021_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", data->value); \
|
||||
}
|
||||
show2(alarms);
|
||||
|
||||
#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 adm1021_data *data = i2c_get_clientdata(client); \
|
||||
int temp = simple_strtoul(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->value = TEMP_TO_REG(temp); \
|
||||
adm1021_write_value(client, reg, data->value); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
}
|
||||
set(temp_max, ADM1021_REG_TOS_W);
|
||||
set(temp_hyst, ADM1021_REG_THYST_W);
|
||||
set(remote_temp_max, ADM1021_REG_REMOTE_TOS_W);
|
||||
set(remote_temp_hyst, ADM1021_REG_REMOTE_THYST_W);
|
||||
|
||||
static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max);
|
||||
static DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp_hyst, set_temp_hyst);
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL);
|
||||
static DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_remote_temp_max, set_remote_temp_max);
|
||||
static DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_remote_temp_hyst, set_remote_temp_hyst);
|
||||
static DEVICE_ATTR(temp2_input, S_IRUGO, show_remote_temp_input, NULL);
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
|
||||
|
||||
static int adm1021_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, adm1021_detect);
|
||||
}
|
||||
|
||||
static int adm1021_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
int i;
|
||||
struct i2c_client *new_client;
|
||||
struct adm1021_data *data;
|
||||
int err = 0;
|
||||
const char *type_name = "";
|
||||
|
||||
/* Make sure we aren't probing the ISA bus!! This is just a safety check
|
||||
at this moment; i2c_detect really won't call us. */
|
||||
#ifdef DEBUG
|
||||
if (i2c_is_isa_adapter(adapter)) {
|
||||
dev_dbg(&adapter->dev, "adm1021_detect called for an ISA bus adapter?!?\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto error0;
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
client structure, even though we cannot fill it completely yet.
|
||||
But it allows us to access adm1021_{read,write}_value. */
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct adm1021_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto error0;
|
||||
}
|
||||
memset(data, 0, sizeof(struct adm1021_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &adm1021_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Now, we do the remaining detection. */
|
||||
if (kind < 0) {
|
||||
if ((adm1021_read_value(new_client, ADM1021_REG_STATUS) & 0x03) != 0x00
|
||||
|| (adm1021_read_value(new_client, ADM1021_REG_CONFIG_R) & 0x3F) != 0x00
|
||||
|| (adm1021_read_value(new_client, ADM1021_REG_CONV_RATE_R) & 0xF8) != 0x00) {
|
||||
err = -ENODEV;
|
||||
goto error1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine the chip type. */
|
||||
if (kind <= 0) {
|
||||
i = adm1021_read_value(new_client, ADM1021_REG_MAN_ID);
|
||||
if (i == 0x41)
|
||||
if ((adm1021_read_value(new_client, ADM1021_REG_DEV_ID) & 0x0F0) == 0x030)
|
||||
kind = adm1023;
|
||||
else
|
||||
kind = adm1021;
|
||||
else if (i == 0x49)
|
||||
kind = thmc10;
|
||||
else if (i == 0x23)
|
||||
kind = gl523sm;
|
||||
else if ((i == 0x4d) &&
|
||||
(adm1021_read_value(new_client, ADM1021_REG_DEV_ID) == 0x01))
|
||||
kind = max1617a;
|
||||
else if (i == 0x54)
|
||||
kind = mc1066;
|
||||
/* LM84 Mfr ID in a different place, and it has more unused bits */
|
||||
else if (adm1021_read_value(new_client, ADM1021_REG_CONV_RATE_R) == 0x00
|
||||
&& (kind == 0 /* skip extra detection */
|
||||
|| ((adm1021_read_value(new_client, ADM1021_REG_CONFIG_R) & 0x7F) == 0x00
|
||||
&& (adm1021_read_value(new_client, ADM1021_REG_STATUS) & 0xAB) == 0x00)))
|
||||
kind = lm84;
|
||||
else
|
||||
kind = max1617;
|
||||
}
|
||||
|
||||
if (kind == max1617) {
|
||||
type_name = "max1617";
|
||||
} else if (kind == max1617a) {
|
||||
type_name = "max1617a";
|
||||
} else if (kind == adm1021) {
|
||||
type_name = "adm1021";
|
||||
} else if (kind == adm1023) {
|
||||
type_name = "adm1023";
|
||||
} else if (kind == thmc10) {
|
||||
type_name = "thmc10";
|
||||
} else if (kind == lm84) {
|
||||
type_name = "lm84";
|
||||
} else if (kind == gl523sm) {
|
||||
type_name = "gl523sm";
|
||||
} else if (kind == mc1066) {
|
||||
type_name = "mc1066";
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields and put it into the global list */
|
||||
strlcpy(new_client->name, type_name, I2C_NAME_SIZE);
|
||||
data->type = kind;
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto error1;
|
||||
|
||||
/* Initialize the ADM1021 chip */
|
||||
if (kind != lm84)
|
||||
adm1021_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
|
||||
return 0;
|
||||
|
||||
error1:
|
||||
kfree(data);
|
||||
error0:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void adm1021_init_client(struct i2c_client *client)
|
||||
{
|
||||
/* Enable ADC and disable suspend mode */
|
||||
adm1021_write_value(client, ADM1021_REG_CONFIG_W,
|
||||
adm1021_read_value(client, ADM1021_REG_CONFIG_R) & 0xBF);
|
||||
/* Set Conversion rate to 1/sec (this can be tinkered with) */
|
||||
adm1021_write_value(client, ADM1021_REG_CONV_RATE_W, 0x04);
|
||||
}
|
||||
|
||||
static int adm1021_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* All registers are byte-sized */
|
||||
static int adm1021_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
static int adm1021_write_value(struct i2c_client *client, u8 reg, u16 value)
|
||||
{
|
||||
if (!read_only)
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct adm1021_data *adm1021_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1021_data *data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
dev_dbg(&client->dev, "Starting adm1021 update\n");
|
||||
|
||||
data->temp_input = adm1021_read_value(client, ADM1021_REG_TEMP);
|
||||
data->temp_max = adm1021_read_value(client, ADM1021_REG_TOS_R);
|
||||
data->temp_hyst = adm1021_read_value(client, ADM1021_REG_THYST_R);
|
||||
data->remote_temp_input = adm1021_read_value(client, ADM1021_REG_REMOTE_TEMP);
|
||||
data->remote_temp_max = adm1021_read_value(client, ADM1021_REG_REMOTE_TOS_R);
|
||||
data->remote_temp_hyst = adm1021_read_value(client, ADM1021_REG_REMOTE_THYST_R);
|
||||
data->alarms = adm1021_read_value(client, ADM1021_REG_STATUS) & 0x7c;
|
||||
if (data->type == adm1023) {
|
||||
data->remote_temp_prec = adm1021_read_value(client, ADM1021_REG_REM_TEMP_PREC);
|
||||
data->remote_temp_os_prec = adm1021_read_value(client, ADM1021_REG_REM_TOS_PREC);
|
||||
data->remote_temp_hyst_prec = adm1021_read_value(client, ADM1021_REG_REM_THYST_PREC);
|
||||
data->remote_temp_offset = adm1021_read_value(client, ADM1021_REG_REM_OFFSET);
|
||||
data->remote_temp_offset_prec = adm1021_read_value(client, ADM1021_REG_REM_OFFSET_PREC);
|
||||
}
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_adm1021_init(void)
|
||||
{
|
||||
return i2c_add_driver(&adm1021_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_adm1021_exit(void)
|
||||
{
|
||||
i2c_del_driver(&adm1021_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR ("Frodo Looijaard <frodol@dds.nl> and "
|
||||
"Philip Edelbrock <phil@netroedge.com>");
|
||||
MODULE_DESCRIPTION("adm1021 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_param(read_only, bool, 0);
|
||||
MODULE_PARM_DESC(read_only, "Don't set any values, read only mode");
|
||||
|
||||
module_init(sensors_adm1021_init)
|
||||
module_exit(sensors_adm1021_exit)
|
||||
577
drivers/hwmon/adm1025.c
Normal file
577
drivers/hwmon/adm1025.c
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
/*
|
||||
* adm1025.c
|
||||
*
|
||||
* Copyright (C) 2000 Chen-Yuan Wu <gwu@esoft.com>
|
||||
* Copyright (C) 2003-2004 Jean Delvare <khali@linux-fr.org>
|
||||
*
|
||||
* The ADM1025 is a sensor chip made by Analog Devices. It reports up to 6
|
||||
* voltages (including its own power source) and up to two temperatures
|
||||
* (its own plus up to one external one). Voltages are scaled internally
|
||||
* (which is not the common way) with ratios such that the nominal value
|
||||
* of each voltage correspond to a register value of 192 (which means a
|
||||
* resolution of about 0.5% of the nominal value). Temperature values are
|
||||
* reported with a 1 deg resolution and a 3 deg accuracy. Complete
|
||||
* datasheet can be obtained from Analog's website at:
|
||||
* http://www.analog.com/Analog_Root/productPage/productHome/0,2121,ADM1025,00.html
|
||||
*
|
||||
* This driver also supports the ADM1025A, which differs from the ADM1025
|
||||
* only in that it has "open-drain VID inputs while the ADM1025 has
|
||||
* on-chip 100k pull-ups on the VID inputs". It doesn't make any
|
||||
* difference for us.
|
||||
*
|
||||
* This driver also supports the NE1619, a sensor chip made by Philips.
|
||||
* That chip is similar to the ADM1025A, with a few differences. The only
|
||||
* difference that matters to us is that the NE1619 has only two possible
|
||||
* addresses while the ADM1025A has a third one. Complete datasheet can be
|
||||
* obtained from Philips's website at:
|
||||
* http://www.semiconductors.philips.com/pip/NE1619DS.html
|
||||
*
|
||||
* Since the ADM1025 was the first chipset supported by this driver, most
|
||||
* comments will refer to this chipset, but are actually general and
|
||||
* concern all supported chipsets, unless mentioned otherwise.
|
||||
*
|
||||
* 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/i2c-sensor.h>
|
||||
#include <linux/i2c-vid.h>
|
||||
|
||||
/*
|
||||
* Addresses to scan
|
||||
* ADM1025 and ADM1025A have three possible addresses: 0x2c, 0x2d and 0x2e.
|
||||
* NE1619 has two possible addresses: 0x2c and 0x2d.
|
||||
*/
|
||||
|
||||
static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/*
|
||||
* Insmod parameters
|
||||
*/
|
||||
|
||||
SENSORS_INSMOD_2(adm1025, ne1619);
|
||||
|
||||
/*
|
||||
* The ADM1025 registers
|
||||
*/
|
||||
|
||||
#define ADM1025_REG_MAN_ID 0x3E
|
||||
#define ADM1025_REG_CHIP_ID 0x3F
|
||||
#define ADM1025_REG_CONFIG 0x40
|
||||
#define ADM1025_REG_STATUS1 0x41
|
||||
#define ADM1025_REG_STATUS2 0x42
|
||||
#define ADM1025_REG_IN(nr) (0x20 + (nr))
|
||||
#define ADM1025_REG_IN_MAX(nr) (0x2B + (nr) * 2)
|
||||
#define ADM1025_REG_IN_MIN(nr) (0x2C + (nr) * 2)
|
||||
#define ADM1025_REG_TEMP(nr) (0x26 + (nr))
|
||||
#define ADM1025_REG_TEMP_HIGH(nr) (0x37 + (nr) * 2)
|
||||
#define ADM1025_REG_TEMP_LOW(nr) (0x38 + (nr) * 2)
|
||||
#define ADM1025_REG_VID 0x47
|
||||
#define ADM1025_REG_VID4 0x49
|
||||
|
||||
/*
|
||||
* Conversions and various macros
|
||||
* The ADM1025 uses signed 8-bit values for temperatures.
|
||||
*/
|
||||
|
||||
static int in_scale[6] = { 2500, 2250, 3300, 5000, 12000, 3300 };
|
||||
|
||||
#define IN_FROM_REG(reg,scale) (((reg) * (scale) + 96) / 192)
|
||||
#define IN_TO_REG(val,scale) ((val) <= 0 ? 0 : \
|
||||
(val) * 192 >= (scale) * 255 ? 255 : \
|
||||
((val) * 192 + (scale)/2) / (scale))
|
||||
|
||||
#define TEMP_FROM_REG(reg) ((reg) * 1000)
|
||||
#define TEMP_TO_REG(val) ((val) <= -127500 ? -128 : \
|
||||
(val) >= 126500 ? 127 : \
|
||||
(((val) < 0 ? (val)-500 : (val)+500) / 1000))
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
*/
|
||||
|
||||
static int adm1025_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int adm1025_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void adm1025_init_client(struct i2c_client *client);
|
||||
static int adm1025_detach_client(struct i2c_client *client);
|
||||
static struct adm1025_data *adm1025_update_device(struct device *dev);
|
||||
|
||||
/*
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
|
||||
static struct i2c_driver adm1025_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "adm1025",
|
||||
.id = I2C_DRIVERID_ADM1025,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = adm1025_attach_adapter,
|
||||
.detach_client = adm1025_detach_client,
|
||||
};
|
||||
|
||||
/*
|
||||
* Client data (each client gets its own)
|
||||
*/
|
||||
|
||||
struct adm1025_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
|
||||
u8 in[6]; /* register value */
|
||||
u8 in_max[6]; /* register value */
|
||||
u8 in_min[6]; /* register value */
|
||||
s8 temp[2]; /* register value */
|
||||
s8 temp_min[2]; /* register value */
|
||||
s8 temp_max[2]; /* register value */
|
||||
u16 alarms; /* register values, combined */
|
||||
u8 vid; /* register values, combined */
|
||||
u8 vrm;
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
#define show_in(offset) \
|
||||
static ssize_t show_in##offset(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct adm1025_data *data = adm1025_update_device(dev); \
|
||||
return sprintf(buf, "%u\n", IN_FROM_REG(data->in[offset], \
|
||||
in_scale[offset])); \
|
||||
} \
|
||||
static ssize_t show_in##offset##_min(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct adm1025_data *data = adm1025_update_device(dev); \
|
||||
return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[offset], \
|
||||
in_scale[offset])); \
|
||||
} \
|
||||
static ssize_t show_in##offset##_max(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct adm1025_data *data = adm1025_update_device(dev); \
|
||||
return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[offset], \
|
||||
in_scale[offset])); \
|
||||
} \
|
||||
static DEVICE_ATTR(in##offset##_input, S_IRUGO, show_in##offset, NULL);
|
||||
show_in(0);
|
||||
show_in(1);
|
||||
show_in(2);
|
||||
show_in(3);
|
||||
show_in(4);
|
||||
show_in(5);
|
||||
|
||||
#define show_temp(offset) \
|
||||
static ssize_t show_temp##offset(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct adm1025_data *data = adm1025_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[offset-1])); \
|
||||
} \
|
||||
static ssize_t show_temp##offset##_min(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct adm1025_data *data = adm1025_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[offset-1])); \
|
||||
} \
|
||||
static ssize_t show_temp##offset##_max(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct adm1025_data *data = adm1025_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[offset-1])); \
|
||||
}\
|
||||
static DEVICE_ATTR(temp##offset##_input, S_IRUGO, show_temp##offset, NULL);
|
||||
show_temp(1);
|
||||
show_temp(2);
|
||||
|
||||
#define set_in(offset) \
|
||||
static ssize_t set_in##offset##_min(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct adm1025_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtol(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->in_min[offset] = IN_TO_REG(val, in_scale[offset]); \
|
||||
i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MIN(offset), \
|
||||
data->in_min[offset]); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
} \
|
||||
static ssize_t set_in##offset##_max(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct adm1025_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtol(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->in_max[offset] = IN_TO_REG(val, in_scale[offset]); \
|
||||
i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MAX(offset), \
|
||||
data->in_max[offset]); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
} \
|
||||
static DEVICE_ATTR(in##offset##_min, S_IWUSR | S_IRUGO, \
|
||||
show_in##offset##_min, set_in##offset##_min); \
|
||||
static DEVICE_ATTR(in##offset##_max, S_IWUSR | S_IRUGO, \
|
||||
show_in##offset##_max, set_in##offset##_max);
|
||||
set_in(0);
|
||||
set_in(1);
|
||||
set_in(2);
|
||||
set_in(3);
|
||||
set_in(4);
|
||||
set_in(5);
|
||||
|
||||
#define set_temp(offset) \
|
||||
static ssize_t set_temp##offset##_min(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct adm1025_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtol(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->temp_min[offset-1] = TEMP_TO_REG(val); \
|
||||
i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_LOW(offset-1), \
|
||||
data->temp_min[offset-1]); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
} \
|
||||
static ssize_t set_temp##offset##_max(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct adm1025_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtol(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->temp_max[offset-1] = TEMP_TO_REG(val); \
|
||||
i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_HIGH(offset-1), \
|
||||
data->temp_max[offset-1]); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
} \
|
||||
static DEVICE_ATTR(temp##offset##_min, S_IWUSR | S_IRUGO, \
|
||||
show_temp##offset##_min, set_temp##offset##_min); \
|
||||
static DEVICE_ATTR(temp##offset##_max, S_IWUSR | S_IRUGO, \
|
||||
show_temp##offset##_max, set_temp##offset##_max);
|
||||
set_temp(1);
|
||||
set_temp(2);
|
||||
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adm1025_data *data = adm1025_update_device(dev);
|
||||
return sprintf(buf, "%u\n", data->alarms);
|
||||
}
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
|
||||
static ssize_t show_vid(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adm1025_data *data = adm1025_update_device(dev);
|
||||
return sprintf(buf, "%u\n", vid_from_reg(data->vid, data->vrm));
|
||||
}
|
||||
/* in1_ref is deprecated in favour of cpu0_vid, remove after 2005-11-11 */
|
||||
static DEVICE_ATTR(in1_ref, S_IRUGO, show_vid, NULL);
|
||||
static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL);
|
||||
|
||||
static ssize_t show_vrm(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adm1025_data *data = adm1025_update_device(dev);
|
||||
return sprintf(buf, "%u\n", data->vrm);
|
||||
}
|
||||
static ssize_t set_vrm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1025_data *data = i2c_get_clientdata(client);
|
||||
data->vrm = simple_strtoul(buf, NULL, 10);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR(vrm, S_IRUGO | S_IWUSR, show_vrm, set_vrm);
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static int adm1025_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, adm1025_detect);
|
||||
}
|
||||
|
||||
/*
|
||||
* The following function does more than just detection. If detection
|
||||
* succeeds, it also registers the new chip.
|
||||
*/
|
||||
static int adm1025_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct adm1025_data *data;
|
||||
int err = 0;
|
||||
const char *name = "";
|
||||
u8 config;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct adm1025_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct adm1025_data));
|
||||
|
||||
/* The common I2C client data is placed right before the
|
||||
ADM1025-specific data. */
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &adm1025_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/*
|
||||
* 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. 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.
|
||||
*/
|
||||
config = i2c_smbus_read_byte_data(new_client, ADM1025_REG_CONFIG);
|
||||
if (kind < 0) { /* detection */
|
||||
if ((config & 0x80) != 0x00
|
||||
|| (i2c_smbus_read_byte_data(new_client,
|
||||
ADM1025_REG_STATUS1) & 0xC0) != 0x00
|
||||
|| (i2c_smbus_read_byte_data(new_client,
|
||||
ADM1025_REG_STATUS2) & 0xBC) != 0x00) {
|
||||
dev_dbg(&adapter->dev,
|
||||
"ADM1025 detection failed at 0x%02x.\n",
|
||||
address);
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind <= 0) { /* identification */
|
||||
u8 man_id, chip_id;
|
||||
|
||||
man_id = i2c_smbus_read_byte_data(new_client,
|
||||
ADM1025_REG_MAN_ID);
|
||||
chip_id = i2c_smbus_read_byte_data(new_client,
|
||||
ADM1025_REG_CHIP_ID);
|
||||
|
||||
if (man_id == 0x41) { /* Analog Devices */
|
||||
if ((chip_id & 0xF0) == 0x20) { /* ADM1025/ADM1025A */
|
||||
kind = adm1025;
|
||||
}
|
||||
} else
|
||||
if (man_id == 0xA1) { /* Philips */
|
||||
if (address != 0x2E
|
||||
&& (chip_id & 0xF0) == 0x20) { /* NE1619 */
|
||||
kind = ne1619;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind <= 0) { /* identification failed */
|
||||
dev_info(&adapter->dev,
|
||||
"Unsupported chip (man_id=0x%02X, "
|
||||
"chip_id=0x%02X).\n", man_id, chip_id);
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == adm1025) {
|
||||
name = "adm1025";
|
||||
} else if (kind == ne1619) {
|
||||
name = "ne1619";
|
||||
}
|
||||
|
||||
/* We can fill in the remaining client fields */
|
||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Initialize the ADM1025 chip */
|
||||
adm1025_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
/* in1_ref is deprecated, remove after 2005-11-11 */
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_ref);
|
||||
device_create_file(&new_client->dev, &dev_attr_cpu0_vid);
|
||||
device_create_file(&new_client->dev, &dev_attr_vrm);
|
||||
|
||||
/* Pin 11 is either in4 (+12V) or VID4 */
|
||||
if (!(config & 0x20)) {
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_max);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void adm1025_init_client(struct i2c_client *client)
|
||||
{
|
||||
u8 reg;
|
||||
struct adm1025_data *data = i2c_get_clientdata(client);
|
||||
int i;
|
||||
|
||||
data->vrm = i2c_which_vrm();
|
||||
|
||||
/*
|
||||
* Set high limits
|
||||
* Usually we avoid setting limits on driver init, but it happens
|
||||
* that the ADM1025 comes with stupid default limits (all registers
|
||||
* set to 0). In case the chip has not gone through any limit
|
||||
* setting yet, we better set the high limits to the max so that
|
||||
* no alarm triggers.
|
||||
*/
|
||||
for (i=0; i<6; i++) {
|
||||
reg = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_IN_MAX(i));
|
||||
if (reg == 0)
|
||||
i2c_smbus_write_byte_data(client,
|
||||
ADM1025_REG_IN_MAX(i),
|
||||
0xFF);
|
||||
}
|
||||
for (i=0; i<2; i++) {
|
||||
reg = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_TEMP_HIGH(i));
|
||||
if (reg == 0)
|
||||
i2c_smbus_write_byte_data(client,
|
||||
ADM1025_REG_TEMP_HIGH(i),
|
||||
0x7F);
|
||||
}
|
||||
|
||||
/*
|
||||
* Start the conversions
|
||||
*/
|
||||
reg = i2c_smbus_read_byte_data(client, ADM1025_REG_CONFIG);
|
||||
if (!(reg & 0x01))
|
||||
i2c_smbus_write_byte_data(client, ADM1025_REG_CONFIG,
|
||||
(reg&0x7E)|0x01);
|
||||
}
|
||||
|
||||
static int adm1025_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, "
|
||||
"client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct adm1025_data *adm1025_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1025_data *data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) {
|
||||
int i;
|
||||
|
||||
dev_dbg(&client->dev, "Updating data.\n");
|
||||
for (i=0; i<6; i++) {
|
||||
data->in[i] = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_IN(i));
|
||||
data->in_min[i] = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_IN_MIN(i));
|
||||
data->in_max[i] = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_IN_MAX(i));
|
||||
}
|
||||
for (i=0; i<2; i++) {
|
||||
data->temp[i] = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_TEMP(i));
|
||||
data->temp_min[i] = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_TEMP_LOW(i));
|
||||
data->temp_max[i] = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_TEMP_HIGH(i));
|
||||
}
|
||||
data->alarms = i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_STATUS1)
|
||||
| (i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_STATUS2) << 8);
|
||||
data->vid = (i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_VID) & 0x0f)
|
||||
| ((i2c_smbus_read_byte_data(client,
|
||||
ADM1025_REG_VID4) & 0x01) << 4);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_adm1025_init(void)
|
||||
{
|
||||
return i2c_add_driver(&adm1025_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_adm1025_exit(void)
|
||||
{
|
||||
i2c_del_driver(&adm1025_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
|
||||
MODULE_DESCRIPTION("ADM1025 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_adm1025_init);
|
||||
module_exit(sensors_adm1025_exit);
|
||||
1714
drivers/hwmon/adm1026.c
Normal file
1714
drivers/hwmon/adm1026.c
Normal file
File diff suppressed because it is too large
Load diff
977
drivers/hwmon/adm1031.c
Normal file
977
drivers/hwmon/adm1031.c
Normal file
|
|
@ -0,0 +1,977 @@
|
|||
/*
|
||||
adm1031.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Based on lm75.c and lm85.c
|
||||
Supports adm1030 / adm1031
|
||||
Copyright (C) 2004 Alexandre d'Alton <alex@alexdalton.org>
|
||||
Reworked by 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
|
||||
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/i2c-sensor.h>
|
||||
|
||||
/* Following macros takes channel parameter starting from 0 to 2 */
|
||||
#define ADM1031_REG_FAN_SPEED(nr) (0x08 + (nr))
|
||||
#define ADM1031_REG_FAN_DIV(nr) (0x20 + (nr))
|
||||
#define ADM1031_REG_PWM (0x22)
|
||||
#define ADM1031_REG_FAN_MIN(nr) (0x10 + (nr))
|
||||
|
||||
#define ADM1031_REG_TEMP_MAX(nr) (0x14 + 4*(nr))
|
||||
#define ADM1031_REG_TEMP_MIN(nr) (0x15 + 4*(nr))
|
||||
#define ADM1031_REG_TEMP_CRIT(nr) (0x16 + 4*(nr))
|
||||
|
||||
#define ADM1031_REG_TEMP(nr) (0xa + (nr))
|
||||
#define ADM1031_REG_AUTO_TEMP(nr) (0x24 + (nr))
|
||||
|
||||
#define ADM1031_REG_STATUS(nr) (0x2 + (nr))
|
||||
|
||||
#define ADM1031_REG_CONF1 0x0
|
||||
#define ADM1031_REG_CONF2 0x1
|
||||
#define ADM1031_REG_EXT_TEMP 0x6
|
||||
|
||||
#define ADM1031_CONF1_MONITOR_ENABLE 0x01 /* Monitoring enable */
|
||||
#define ADM1031_CONF1_PWM_INVERT 0x08 /* PWM Invert */
|
||||
#define ADM1031_CONF1_AUTO_MODE 0x80 /* Auto FAN */
|
||||
|
||||
#define ADM1031_CONF2_PWM1_ENABLE 0x01
|
||||
#define ADM1031_CONF2_PWM2_ENABLE 0x02
|
||||
#define ADM1031_CONF2_TACH1_ENABLE 0x04
|
||||
#define ADM1031_CONF2_TACH2_ENABLE 0x08
|
||||
#define ADM1031_CONF2_TEMP_ENABLE(chan) (0x10 << (chan))
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_2(adm1030, adm1031);
|
||||
|
||||
typedef u8 auto_chan_table_t[8][2];
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct adm1031_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
int chip_type;
|
||||
char valid; /* !=0 if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
/* The chan_select_table contains the possible configurations for
|
||||
* auto fan control.
|
||||
*/
|
||||
auto_chan_table_t *chan_select_table;
|
||||
u16 alarm;
|
||||
u8 conf1;
|
||||
u8 conf2;
|
||||
u8 fan[2];
|
||||
u8 fan_div[2];
|
||||
u8 fan_min[2];
|
||||
u8 pwm[2];
|
||||
u8 old_pwm[2];
|
||||
s8 temp[3];
|
||||
u8 ext_temp[3];
|
||||
u8 auto_temp[3];
|
||||
u8 auto_temp_min[3];
|
||||
u8 auto_temp_off[3];
|
||||
u8 auto_temp_max[3];
|
||||
s8 temp_min[3];
|
||||
s8 temp_max[3];
|
||||
s8 temp_crit[3];
|
||||
};
|
||||
|
||||
static int adm1031_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int adm1031_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void adm1031_init_client(struct i2c_client *client);
|
||||
static int adm1031_detach_client(struct i2c_client *client);
|
||||
static struct adm1031_data *adm1031_update_device(struct device *dev);
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver adm1031_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "adm1031",
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = adm1031_attach_adapter,
|
||||
.detach_client = adm1031_detach_client,
|
||||
};
|
||||
|
||||
static inline u8 adm1031_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
static inline int
|
||||
adm1031_write_value(struct i2c_client *client, u8 reg, unsigned int value)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
|
||||
#define TEMP_TO_REG(val) (((val) < 0 ? ((val - 500) / 1000) : \
|
||||
((val + 500) / 1000)))
|
||||
|
||||
#define TEMP_FROM_REG(val) ((val) * 1000)
|
||||
|
||||
#define TEMP_FROM_REG_EXT(val, ext) (TEMP_FROM_REG(val) + (ext) * 125)
|
||||
|
||||
#define FAN_FROM_REG(reg, div) ((reg) ? (11250 * 60) / ((reg) * (div)) : 0)
|
||||
|
||||
static int FAN_TO_REG(int reg, int div)
|
||||
{
|
||||
int tmp;
|
||||
tmp = FAN_FROM_REG(SENSORS_LIMIT(reg, 0, 65535), div);
|
||||
return tmp > 255 ? 255 : tmp;
|
||||
}
|
||||
|
||||
#define FAN_DIV_FROM_REG(reg) (1<<(((reg)&0xc0)>>6))
|
||||
|
||||
#define PWM_TO_REG(val) (SENSORS_LIMIT((val), 0, 255) >> 4)
|
||||
#define PWM_FROM_REG(val) ((val) << 4)
|
||||
|
||||
#define FAN_CHAN_FROM_REG(reg) (((reg) >> 5) & 7)
|
||||
#define FAN_CHAN_TO_REG(val, reg) \
|
||||
(((reg) & 0x1F) | (((val) << 5) & 0xe0))
|
||||
|
||||
#define AUTO_TEMP_MIN_TO_REG(val, reg) \
|
||||
((((val)/500) & 0xf8)|((reg) & 0x7))
|
||||
#define AUTO_TEMP_RANGE_FROM_REG(reg) (5000 * (1<< ((reg)&0x7)))
|
||||
#define AUTO_TEMP_MIN_FROM_REG(reg) (1000 * ((((reg) >> 3) & 0x1f) << 2))
|
||||
|
||||
#define AUTO_TEMP_MIN_FROM_REG_DEG(reg) ((((reg) >> 3) & 0x1f) << 2)
|
||||
|
||||
#define AUTO_TEMP_OFF_FROM_REG(reg) \
|
||||
(AUTO_TEMP_MIN_FROM_REG(reg) - 5000)
|
||||
|
||||
#define AUTO_TEMP_MAX_FROM_REG(reg) \
|
||||
(AUTO_TEMP_RANGE_FROM_REG(reg) + \
|
||||
AUTO_TEMP_MIN_FROM_REG(reg))
|
||||
|
||||
static int AUTO_TEMP_MAX_TO_REG(int val, int reg, int pwm)
|
||||
{
|
||||
int ret;
|
||||
int range = val - AUTO_TEMP_MIN_FROM_REG(reg);
|
||||
|
||||
range = ((val - AUTO_TEMP_MIN_FROM_REG(reg))*10)/(16 - pwm);
|
||||
ret = ((reg & 0xf8) |
|
||||
(range < 10000 ? 0 :
|
||||
range < 20000 ? 1 :
|
||||
range < 40000 ? 2 : range < 80000 ? 3 : 4));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* FAN auto control */
|
||||
#define GET_FAN_AUTO_BITFIELD(data, idx) \
|
||||
(*(data)->chan_select_table)[FAN_CHAN_FROM_REG((data)->conf1)][idx%2]
|
||||
|
||||
/* The tables below contains the possible values for the auto fan
|
||||
* control bitfields. the index in the table is the register value.
|
||||
* MSb is the auto fan control enable bit, so the four first entries
|
||||
* in the table disables auto fan control when both bitfields are zero.
|
||||
*/
|
||||
static auto_chan_table_t auto_channel_select_table_adm1031 = {
|
||||
{0, 0}, {0, 0}, {0, 0}, {0, 0},
|
||||
{2 /*0b010 */ , 4 /*0b100 */ },
|
||||
{2 /*0b010 */ , 2 /*0b010 */ },
|
||||
{4 /*0b100 */ , 4 /*0b100 */ },
|
||||
{7 /*0b111 */ , 7 /*0b111 */ },
|
||||
};
|
||||
|
||||
static auto_chan_table_t auto_channel_select_table_adm1030 = {
|
||||
{0, 0}, {0, 0}, {0, 0}, {0, 0},
|
||||
{2 /*0b10 */ , 0},
|
||||
{0xff /*invalid */ , 0},
|
||||
{0xff /*invalid */ , 0},
|
||||
{3 /*0b11 */ , 0},
|
||||
};
|
||||
|
||||
/* That function checks if a bitfield is valid and returns the other bitfield
|
||||
* nearest match if no exact match where found.
|
||||
*/
|
||||
static int
|
||||
get_fan_auto_nearest(struct adm1031_data *data,
|
||||
int chan, u8 val, u8 reg, u8 * new_reg)
|
||||
{
|
||||
int i;
|
||||
int first_match = -1, exact_match = -1;
|
||||
u8 other_reg_val =
|
||||
(*data->chan_select_table)[FAN_CHAN_FROM_REG(reg)][chan ? 0 : 1];
|
||||
|
||||
if (val == 0) {
|
||||
*new_reg = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if ((val == (*data->chan_select_table)[i][chan]) &&
|
||||
((*data->chan_select_table)[i][chan ? 0 : 1] ==
|
||||
other_reg_val)) {
|
||||
/* We found an exact match */
|
||||
exact_match = i;
|
||||
break;
|
||||
} else if (val == (*data->chan_select_table)[i][chan] &&
|
||||
first_match == -1) {
|
||||
/* Save the first match in case of an exact match has not been
|
||||
* found
|
||||
*/
|
||||
first_match = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (exact_match >= 0) {
|
||||
*new_reg = exact_match;
|
||||
} else if (first_match >= 0) {
|
||||
*new_reg = first_match;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t show_fan_auto_channel(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n", GET_FAN_AUTO_BITFIELD(data, nr));
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
set_fan_auto_channel(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val = simple_strtol(buf, NULL, 10);
|
||||
u8 reg;
|
||||
int ret;
|
||||
u8 old_fan_mode;
|
||||
|
||||
old_fan_mode = data->conf1;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if ((ret = get_fan_auto_nearest(data, nr, val, data->conf1, ®))) {
|
||||
up(&data->update_lock);
|
||||
return ret;
|
||||
}
|
||||
if (((data->conf1 = FAN_CHAN_TO_REG(reg, data->conf1)) & ADM1031_CONF1_AUTO_MODE) ^
|
||||
(old_fan_mode & ADM1031_CONF1_AUTO_MODE)) {
|
||||
if (data->conf1 & ADM1031_CONF1_AUTO_MODE){
|
||||
/* Switch to Auto Fan Mode
|
||||
* Save PWM registers
|
||||
* Set PWM registers to 33% Both */
|
||||
data->old_pwm[0] = data->pwm[0];
|
||||
data->old_pwm[1] = data->pwm[1];
|
||||
adm1031_write_value(client, ADM1031_REG_PWM, 0x55);
|
||||
} else {
|
||||
/* Switch to Manual Mode */
|
||||
data->pwm[0] = data->old_pwm[0];
|
||||
data->pwm[1] = data->old_pwm[1];
|
||||
/* Restore PWM registers */
|
||||
adm1031_write_value(client, ADM1031_REG_PWM,
|
||||
data->pwm[0] | (data->pwm[1] << 4));
|
||||
}
|
||||
}
|
||||
data->conf1 = FAN_CHAN_TO_REG(reg, data->conf1);
|
||||
adm1031_write_value(client, ADM1031_REG_CONF1, data->conf1);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define fan_auto_channel_offset(offset) \
|
||||
static ssize_t show_fan_auto_channel_##offset (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_fan_auto_channel(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_fan_auto_channel_##offset (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_fan_auto_channel(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static DEVICE_ATTR(auto_fan##offset##_channel, S_IRUGO | S_IWUSR, \
|
||||
show_fan_auto_channel_##offset, \
|
||||
set_fan_auto_channel_##offset)
|
||||
|
||||
fan_auto_channel_offset(1);
|
||||
fan_auto_channel_offset(2);
|
||||
|
||||
/* Auto Temps */
|
||||
static ssize_t show_auto_temp_off(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n",
|
||||
AUTO_TEMP_OFF_FROM_REG(data->auto_temp[nr]));
|
||||
}
|
||||
static ssize_t show_auto_temp_min(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n",
|
||||
AUTO_TEMP_MIN_FROM_REG(data->auto_temp[nr]));
|
||||
}
|
||||
static ssize_t
|
||||
set_auto_temp_min(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->auto_temp[nr] = AUTO_TEMP_MIN_TO_REG(val, data->auto_temp[nr]);
|
||||
adm1031_write_value(client, ADM1031_REG_AUTO_TEMP(nr),
|
||||
data->auto_temp[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
static ssize_t show_auto_temp_max(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n",
|
||||
AUTO_TEMP_MAX_FROM_REG(data->auto_temp[nr]));
|
||||
}
|
||||
static ssize_t
|
||||
set_auto_temp_max(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp_max[nr] = AUTO_TEMP_MAX_TO_REG(val, data->auto_temp[nr], data->pwm[nr]);
|
||||
adm1031_write_value(client, ADM1031_REG_AUTO_TEMP(nr),
|
||||
data->temp_max[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define auto_temp_reg(offset) \
|
||||
static ssize_t show_auto_temp_##offset##_off (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_auto_temp_off(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t show_auto_temp_##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_auto_temp_min(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t show_auto_temp_##offset##_max (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_auto_temp_max(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_auto_temp_##offset##_min (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_auto_temp_min(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_auto_temp_##offset##_max (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_auto_temp_max(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static DEVICE_ATTR(auto_temp##offset##_off, S_IRUGO, \
|
||||
show_auto_temp_##offset##_off, NULL); \
|
||||
static DEVICE_ATTR(auto_temp##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
show_auto_temp_##offset##_min, set_auto_temp_##offset##_min);\
|
||||
static DEVICE_ATTR(auto_temp##offset##_max, S_IRUGO | S_IWUSR, \
|
||||
show_auto_temp_##offset##_max, set_auto_temp_##offset##_max)
|
||||
|
||||
auto_temp_reg(1);
|
||||
auto_temp_reg(2);
|
||||
auto_temp_reg(3);
|
||||
|
||||
/* pwm */
|
||||
static ssize_t show_pwm(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[nr]));
|
||||
}
|
||||
static ssize_t
|
||||
set_pwm(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val = simple_strtol(buf, NULL, 10);
|
||||
int reg;
|
||||
|
||||
down(&data->update_lock);
|
||||
if ((data->conf1 & ADM1031_CONF1_AUTO_MODE) &&
|
||||
(((val>>4) & 0xf) != 5)) {
|
||||
/* In automatic mode, the only PWM accepted is 33% */
|
||||
up(&data->update_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
data->pwm[nr] = PWM_TO_REG(val);
|
||||
reg = adm1031_read_value(client, ADM1031_REG_PWM);
|
||||
adm1031_write_value(client, ADM1031_REG_PWM,
|
||||
nr ? ((data->pwm[nr] << 4) & 0xf0) | (reg & 0xf)
|
||||
: (data->pwm[nr] & 0xf) | (reg & 0xf0));
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define pwm_reg(offset) \
|
||||
static ssize_t show_pwm_##offset (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_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 DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR, \
|
||||
show_pwm_##offset, set_pwm_##offset)
|
||||
|
||||
pwm_reg(1);
|
||||
pwm_reg(2);
|
||||
|
||||
/* Fans */
|
||||
|
||||
/*
|
||||
* That function checks the cases where the fan reading is not
|
||||
* relevant. It is used to provide 0 as fan reading when the fan is
|
||||
* not supposed to run
|
||||
*/
|
||||
static int trust_fan_readings(struct adm1031_data *data, int chan)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if (data->conf1 & ADM1031_CONF1_AUTO_MODE) {
|
||||
switch (data->conf1 & 0x60) {
|
||||
case 0x00: /* remote temp1 controls fan1 remote temp2 controls fan2 */
|
||||
res = data->temp[chan+1] >=
|
||||
AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[chan+1]);
|
||||
break;
|
||||
case 0x20: /* remote temp1 controls both fans */
|
||||
res =
|
||||
data->temp[1] >=
|
||||
AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[1]);
|
||||
break;
|
||||
case 0x40: /* remote temp2 controls both fans */
|
||||
res =
|
||||
data->temp[2] >=
|
||||
AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[2]);
|
||||
break;
|
||||
case 0x60: /* max controls both fans */
|
||||
res =
|
||||
data->temp[0] >=
|
||||
AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[0])
|
||||
|| data->temp[1] >=
|
||||
AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[1])
|
||||
|| (data->chip_type == adm1031
|
||||
&& data->temp[2] >=
|
||||
AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[2]));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
res = data->pwm[chan] > 0;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t show_fan(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
int value;
|
||||
|
||||
value = trust_fan_readings(data, nr) ? FAN_FROM_REG(data->fan[nr],
|
||||
FAN_DIV_FROM_REG(data->fan_div[nr])) : 0;
|
||||
return sprintf(buf, "%d\n", value);
|
||||
}
|
||||
|
||||
static ssize_t show_fan_div(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n", FAN_DIV_FROM_REG(data->fan_div[nr]));
|
||||
}
|
||||
static ssize_t show_fan_min(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n",
|
||||
FAN_FROM_REG(data->fan_min[nr],
|
||||
FAN_DIV_FROM_REG(data->fan_div[nr])));
|
||||
}
|
||||
static ssize_t
|
||||
set_fan_min(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
if (val) {
|
||||
data->fan_min[nr] =
|
||||
FAN_TO_REG(val, FAN_DIV_FROM_REG(data->fan_div[nr]));
|
||||
} else {
|
||||
data->fan_min[nr] = 0xff;
|
||||
}
|
||||
adm1031_write_value(client, ADM1031_REG_FAN_MIN(nr), data->fan_min[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
static ssize_t
|
||||
set_fan_div(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val = simple_strtol(buf, NULL, 10);
|
||||
u8 tmp;
|
||||
int old_div;
|
||||
int new_min;
|
||||
|
||||
tmp = val == 8 ? 0xc0 :
|
||||
val == 4 ? 0x80 :
|
||||
val == 2 ? 0x40 :
|
||||
val == 1 ? 0x00 :
|
||||
0xff;
|
||||
if (tmp == 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
down(&data->update_lock);
|
||||
old_div = FAN_DIV_FROM_REG(data->fan_div[nr]);
|
||||
data->fan_div[nr] = (tmp & 0xC0) | (0x3f & data->fan_div[nr]);
|
||||
new_min = data->fan_min[nr] * old_div /
|
||||
FAN_DIV_FROM_REG(data->fan_div[nr]);
|
||||
data->fan_min[nr] = new_min > 0xff ? 0xff : new_min;
|
||||
data->fan[nr] = data->fan[nr] * old_div /
|
||||
FAN_DIV_FROM_REG(data->fan_div[nr]);
|
||||
|
||||
adm1031_write_value(client, ADM1031_REG_FAN_DIV(nr),
|
||||
data->fan_div[nr]);
|
||||
adm1031_write_value(client, ADM1031_REG_FAN_MIN(nr),
|
||||
data->fan_min[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define 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 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 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 DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \
|
||||
show_fan_##offset##_div, set_fan_##offset##_div); \
|
||||
static DEVICE_ATTR(auto_fan##offset##_min_pwm, S_IRUGO | S_IWUSR, \
|
||||
show_pwm_##offset, set_pwm_##offset)
|
||||
|
||||
fan_offset(1);
|
||||
fan_offset(2);
|
||||
|
||||
|
||||
/* Temps */
|
||||
static ssize_t show_temp(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
int ext;
|
||||
ext = nr == 0 ?
|
||||
((data->ext_temp[nr] >> 6) & 0x3) * 2 :
|
||||
(((data->ext_temp[nr] >> ((nr - 1) * 3)) & 7));
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG_EXT(data->temp[nr], ext));
|
||||
}
|
||||
static ssize_t show_temp_min(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[nr]));
|
||||
}
|
||||
static ssize_t show_temp_max(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[nr]));
|
||||
}
|
||||
static ssize_t show_temp_crit(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_crit[nr]));
|
||||
}
|
||||
static ssize_t
|
||||
set_temp_min(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val;
|
||||
|
||||
val = simple_strtol(buf, NULL, 10);
|
||||
val = SENSORS_LIMIT(val, -55000, nr == 0 ? 127750 : 127875);
|
||||
down(&data->update_lock);
|
||||
data->temp_min[nr] = TEMP_TO_REG(val);
|
||||
adm1031_write_value(client, ADM1031_REG_TEMP_MIN(nr),
|
||||
data->temp_min[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
static ssize_t
|
||||
set_temp_max(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val;
|
||||
|
||||
val = simple_strtol(buf, NULL, 10);
|
||||
val = SENSORS_LIMIT(val, -55000, nr == 0 ? 127750 : 127875);
|
||||
down(&data->update_lock);
|
||||
data->temp_max[nr] = TEMP_TO_REG(val);
|
||||
adm1031_write_value(client, ADM1031_REG_TEMP_MAX(nr),
|
||||
data->temp_max[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
static ssize_t
|
||||
set_temp_crit(struct device *dev, const char *buf, size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int val;
|
||||
|
||||
val = simple_strtol(buf, NULL, 10);
|
||||
val = SENSORS_LIMIT(val, -55000, nr == 0 ? 127750 : 127875);
|
||||
down(&data->update_lock);
|
||||
data->temp_crit[nr] = TEMP_TO_REG(val);
|
||||
adm1031_write_value(client, ADM1031_REG_TEMP_CRIT(nr),
|
||||
data->temp_crit[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define temp_reg(offset) \
|
||||
static ssize_t show_temp_##offset (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_temp(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t show_temp_##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_temp_min(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t show_temp_##offset##_max (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_temp_max(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t show_temp_##offset##_crit (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return show_temp_crit(dev, buf, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_temp_##offset##_min (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_temp_min(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_temp_##offset##_max (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_temp_max(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static ssize_t set_temp_##offset##_crit (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return set_temp_crit(dev, buf, count, offset - 1); \
|
||||
} \
|
||||
static DEVICE_ATTR(temp##offset##_input, S_IRUGO, show_temp_##offset, \
|
||||
NULL); \
|
||||
static DEVICE_ATTR(temp##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
show_temp_##offset##_min, set_temp_##offset##_min); \
|
||||
static DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \
|
||||
show_temp_##offset##_max, set_temp_##offset##_max); \
|
||||
static DEVICE_ATTR(temp##offset##_crit, S_IRUGO | S_IWUSR, \
|
||||
show_temp_##offset##_crit, set_temp_##offset##_crit)
|
||||
|
||||
temp_reg(1);
|
||||
temp_reg(2);
|
||||
temp_reg(3);
|
||||
|
||||
/* Alarms */
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adm1031_data *data = adm1031_update_device(dev);
|
||||
return sprintf(buf, "%d\n", data->alarm);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
|
||||
|
||||
static int adm1031_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, adm1031_detect);
|
||||
}
|
||||
|
||||
/* This function is called by i2c_detect */
|
||||
static int adm1031_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct adm1031_data *data;
|
||||
int err = 0;
|
||||
const char *name = "";
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct adm1031_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct adm1031_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &adm1031_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
if (kind < 0) {
|
||||
int id, co;
|
||||
id = i2c_smbus_read_byte_data(new_client, 0x3d);
|
||||
co = i2c_smbus_read_byte_data(new_client, 0x3e);
|
||||
|
||||
if (!((id == 0x31 || id == 0x30) && co == 0x41))
|
||||
goto exit_free;
|
||||
kind = (id == 0x30) ? adm1030 : adm1031;
|
||||
}
|
||||
|
||||
if (kind <= 0)
|
||||
kind = adm1031;
|
||||
|
||||
/* Given the detected chip type, set the chip name and the
|
||||
* auto fan control helper table. */
|
||||
if (kind == adm1030) {
|
||||
name = "adm1030";
|
||||
data->chan_select_table = &auto_channel_select_table_adm1030;
|
||||
} else if (kind == adm1031) {
|
||||
name = "adm1031";
|
||||
data->chan_select_table = &auto_channel_select_table_adm1031;
|
||||
}
|
||||
data->chip_type = kind;
|
||||
|
||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Initialize the ADM1031 chip */
|
||||
adm1031_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_pwm1);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_fan1_channel);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_crit);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_crit);
|
||||
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp1_off);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp1_max);
|
||||
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp2_off);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp2_max);
|
||||
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_fan1_min_pwm);
|
||||
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
|
||||
if (kind == adm1031) {
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_pwm2);
|
||||
device_create_file(&new_client->dev,
|
||||
&dev_attr_auto_fan2_channel);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp3_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp3_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp3_crit);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp3_off);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp3_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_temp3_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_auto_fan2_min_pwm);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(new_client);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int adm1031_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
if ((ret = i2c_detach_client(client)) != 0) {
|
||||
return ret;
|
||||
}
|
||||
kfree(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void adm1031_init_client(struct i2c_client *client)
|
||||
{
|
||||
unsigned int read_val;
|
||||
unsigned int mask;
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
|
||||
mask = (ADM1031_CONF2_PWM1_ENABLE | ADM1031_CONF2_TACH1_ENABLE);
|
||||
if (data->chip_type == adm1031) {
|
||||
mask |= (ADM1031_CONF2_PWM2_ENABLE |
|
||||
ADM1031_CONF2_TACH2_ENABLE);
|
||||
}
|
||||
/* Initialize the ADM1031 chip (enables fan speed reading ) */
|
||||
read_val = adm1031_read_value(client, ADM1031_REG_CONF2);
|
||||
if ((read_val | mask) != read_val) {
|
||||
adm1031_write_value(client, ADM1031_REG_CONF2, read_val | mask);
|
||||
}
|
||||
|
||||
read_val = adm1031_read_value(client, ADM1031_REG_CONF1);
|
||||
if ((read_val | ADM1031_CONF1_MONITOR_ENABLE) != read_val) {
|
||||
adm1031_write_value(client, ADM1031_REG_CONF1, read_val |
|
||||
ADM1031_CONF1_MONITOR_ENABLE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static struct adm1031_data *adm1031_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm1031_data *data = i2c_get_clientdata(client);
|
||||
int chan;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
|
||||
dev_dbg(&client->dev, "Starting adm1031 update\n");
|
||||
for (chan = 0;
|
||||
chan < ((data->chip_type == adm1031) ? 3 : 2); chan++) {
|
||||
u8 oldh, newh;
|
||||
|
||||
oldh =
|
||||
adm1031_read_value(client, ADM1031_REG_TEMP(chan));
|
||||
data->ext_temp[chan] =
|
||||
adm1031_read_value(client, ADM1031_REG_EXT_TEMP);
|
||||
newh =
|
||||
adm1031_read_value(client, ADM1031_REG_TEMP(chan));
|
||||
if (newh != oldh) {
|
||||
data->ext_temp[chan] =
|
||||
adm1031_read_value(client,
|
||||
ADM1031_REG_EXT_TEMP);
|
||||
#ifdef DEBUG
|
||||
oldh =
|
||||
adm1031_read_value(client,
|
||||
ADM1031_REG_TEMP(chan));
|
||||
|
||||
/* oldh is actually newer */
|
||||
if (newh != oldh)
|
||||
dev_warn(&client->dev,
|
||||
"Remote temperature may be "
|
||||
"wrong.\n");
|
||||
#endif
|
||||
}
|
||||
data->temp[chan] = newh;
|
||||
|
||||
data->temp_min[chan] =
|
||||
adm1031_read_value(client,
|
||||
ADM1031_REG_TEMP_MIN(chan));
|
||||
data->temp_max[chan] =
|
||||
adm1031_read_value(client,
|
||||
ADM1031_REG_TEMP_MAX(chan));
|
||||
data->temp_crit[chan] =
|
||||
adm1031_read_value(client,
|
||||
ADM1031_REG_TEMP_CRIT(chan));
|
||||
data->auto_temp[chan] =
|
||||
adm1031_read_value(client,
|
||||
ADM1031_REG_AUTO_TEMP(chan));
|
||||
|
||||
}
|
||||
|
||||
data->conf1 = adm1031_read_value(client, ADM1031_REG_CONF1);
|
||||
data->conf2 = adm1031_read_value(client, ADM1031_REG_CONF2);
|
||||
|
||||
data->alarm = adm1031_read_value(client, ADM1031_REG_STATUS(0))
|
||||
| (adm1031_read_value(client, ADM1031_REG_STATUS(1))
|
||||
<< 8);
|
||||
if (data->chip_type == adm1030) {
|
||||
data->alarm &= 0xc0ff;
|
||||
}
|
||||
|
||||
for (chan=0; chan<(data->chip_type == adm1030 ? 1 : 2); chan++) {
|
||||
data->fan_div[chan] =
|
||||
adm1031_read_value(client, ADM1031_REG_FAN_DIV(chan));
|
||||
data->fan_min[chan] =
|
||||
adm1031_read_value(client, ADM1031_REG_FAN_MIN(chan));
|
||||
data->fan[chan] =
|
||||
adm1031_read_value(client, ADM1031_REG_FAN_SPEED(chan));
|
||||
data->pwm[chan] =
|
||||
0xf & (adm1031_read_value(client, ADM1031_REG_PWM) >>
|
||||
(4*chan));
|
||||
}
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_adm1031_init(void)
|
||||
{
|
||||
return i2c_add_driver(&adm1031_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_adm1031_exit(void)
|
||||
{
|
||||
i2c_del_driver(&adm1031_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Alexandre d'Alton <alex@alexdalton.org>");
|
||||
MODULE_DESCRIPTION("ADM1031/ADM1030 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_adm1031_init);
|
||||
module_exit(sensors_adm1031_exit);
|
||||
791
drivers/hwmon/adm9240.c
Normal file
791
drivers/hwmon/adm9240.c
Normal file
|
|
@ -0,0 +1,791 @@
|
|||
/*
|
||||
* adm9240.c Part of lm_sensors, Linux kernel modules for hardware
|
||||
* monitoring
|
||||
*
|
||||
* Copyright (C) 1999 Frodo Looijaard <frodol@dds.nl>
|
||||
* Philip Edelbrock <phil@netroedge.com>
|
||||
* Copyright (C) 2003 Michiel Rook <michiel@grendelproject.nl>
|
||||
* Copyright (C) 2005 Grant Coady <gcoady@gmail.com> with valuable
|
||||
* guidance from Jean Delvare
|
||||
*
|
||||
* Driver supports Analog Devices ADM9240
|
||||
* Dallas Semiconductor DS1780
|
||||
* National Semiconductor LM81
|
||||
*
|
||||
* ADM9240 is the reference, DS1780 and LM81 are register compatibles
|
||||
*
|
||||
* Voltage Six inputs are scaled by chip, VID also reported
|
||||
* Temperature Chip temperature to 0.5'C, maximum and max_hysteris
|
||||
* Fans 2 fans, low speed alarm, automatic fan clock divider
|
||||
* Alarms 16-bit map of active alarms
|
||||
* Analog Out 0..1250 mV output
|
||||
*
|
||||
* Chassis Intrusion: clear CI latch with 'echo 1 > chassis_clear'
|
||||
*
|
||||
* Test hardware: Intel SE440BX-2 desktop motherboard --Grant
|
||||
*
|
||||
* LM81 extended temp reading not implemented
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-sensor.h>
|
||||
#include <linux/i2c-vid.h>
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f,
|
||||
I2C_CLIENT_END };
|
||||
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_3(adm9240, ds1780, lm81);
|
||||
|
||||
/* ADM9240 registers */
|
||||
#define ADM9240_REG_MAN_ID 0x3e
|
||||
#define ADM9240_REG_DIE_REV 0x3f
|
||||
#define ADM9240_REG_CONFIG 0x40
|
||||
|
||||
#define ADM9240_REG_IN(nr) (0x20 + (nr)) /* 0..5 */
|
||||
#define ADM9240_REG_IN_MAX(nr) (0x2b + (nr) * 2)
|
||||
#define ADM9240_REG_IN_MIN(nr) (0x2c + (nr) * 2)
|
||||
#define ADM9240_REG_FAN(nr) (0x28 + (nr)) /* 0..1 */
|
||||
#define ADM9240_REG_FAN_MIN(nr) (0x3b + (nr))
|
||||
#define ADM9240_REG_INT(nr) (0x41 + (nr))
|
||||
#define ADM9240_REG_INT_MASK(nr) (0x43 + (nr))
|
||||
#define ADM9240_REG_TEMP 0x27
|
||||
#define ADM9240_REG_TEMP_HIGH 0x39
|
||||
#define ADM9240_REG_TEMP_HYST 0x3a
|
||||
#define ADM9240_REG_ANALOG_OUT 0x19
|
||||
#define ADM9240_REG_CHASSIS_CLEAR 0x46
|
||||
#define ADM9240_REG_VID_FAN_DIV 0x47
|
||||
#define ADM9240_REG_I2C_ADDR 0x48
|
||||
#define ADM9240_REG_VID4 0x49
|
||||
#define ADM9240_REG_TEMP_CONF 0x4b
|
||||
|
||||
/* generalised scaling with integer rounding */
|
||||
static inline int SCALE(long val, int mul, int div)
|
||||
{
|
||||
if (val < 0)
|
||||
return (val * mul - div / 2) / div;
|
||||
else
|
||||
return (val * mul + div / 2) / div;
|
||||
}
|
||||
|
||||
/* adm9240 internally scales voltage measurements */
|
||||
static const u16 nom_mv[] = { 2500, 2700, 3300, 5000, 12000, 2700 };
|
||||
|
||||
static inline unsigned int IN_FROM_REG(u8 reg, int n)
|
||||
{
|
||||
return SCALE(reg, nom_mv[n], 192);
|
||||
}
|
||||
|
||||
static inline u8 IN_TO_REG(unsigned long val, int n)
|
||||
{
|
||||
return SENSORS_LIMIT(SCALE(val, 192, nom_mv[n]), 0, 255);
|
||||
}
|
||||
|
||||
/* temperature range: -40..125, 127 disables temperature alarm */
|
||||
static inline s8 TEMP_TO_REG(long val)
|
||||
{
|
||||
return SENSORS_LIMIT(SCALE(val, 1, 1000), -40, 127);
|
||||
}
|
||||
|
||||
/* two fans, each with low fan speed limit */
|
||||
static inline unsigned int FAN_FROM_REG(u8 reg, u8 div)
|
||||
{
|
||||
if (!reg) /* error */
|
||||
return -1;
|
||||
|
||||
if (reg == 255)
|
||||
return 0;
|
||||
|
||||
return SCALE(1350000, 1, reg * div);
|
||||
}
|
||||
|
||||
/* analog out 0..1250mV */
|
||||
static inline u8 AOUT_TO_REG(unsigned long val)
|
||||
{
|
||||
return SENSORS_LIMIT(SCALE(val, 255, 1250), 0, 255);
|
||||
}
|
||||
|
||||
static inline unsigned int AOUT_FROM_REG(u8 reg)
|
||||
{
|
||||
return SCALE(reg, 1250, 255);
|
||||
}
|
||||
|
||||
static int adm9240_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int adm9240_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void adm9240_init_client(struct i2c_client *client);
|
||||
static int adm9240_detach_client(struct i2c_client *client);
|
||||
static struct adm9240_data *adm9240_update_device(struct device *dev);
|
||||
|
||||
/* driver data */
|
||||
static struct i2c_driver adm9240_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "adm9240",
|
||||
.id = I2C_DRIVERID_ADM9240,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = adm9240_attach_adapter,
|
||||
.detach_client = adm9240_detach_client,
|
||||
};
|
||||
|
||||
/* per client data */
|
||||
struct adm9240_data {
|
||||
enum chips type;
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid;
|
||||
unsigned long last_updated_measure;
|
||||
unsigned long last_updated_config;
|
||||
|
||||
u8 in[6]; /* ro in0_input */
|
||||
u8 in_max[6]; /* rw in0_max */
|
||||
u8 in_min[6]; /* rw in0_min */
|
||||
u8 fan[2]; /* ro fan1_input */
|
||||
u8 fan_min[2]; /* rw fan1_min */
|
||||
u8 fan_div[2]; /* rw fan1_div, read-only accessor */
|
||||
s16 temp; /* ro temp1_input, 9-bit sign-extended */
|
||||
s8 temp_high; /* rw temp1_max */
|
||||
s8 temp_hyst; /* rw temp1_max_hyst */
|
||||
u16 alarms; /* ro alarms */
|
||||
u8 aout; /* rw aout_output */
|
||||
u8 vid; /* ro vid */
|
||||
u8 vrm; /* -- vrm set on startup, no accessor */
|
||||
};
|
||||
|
||||
/* i2c byte read/write interface */
|
||||
static int adm9240_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
static int adm9240_write_value(struct i2c_client *client, u8 reg, u8 value)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
/*** sysfs accessors ***/
|
||||
|
||||
/* temperature */
|
||||
#define show_temp(value, scale) \
|
||||
static ssize_t show_##value(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct adm9240_data *data = adm9240_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", data->value * scale); \
|
||||
}
|
||||
show_temp(temp_high, 1000);
|
||||
show_temp(temp_hyst, 1000);
|
||||
show_temp(temp, 500); /* 0.5'C per bit */
|
||||
|
||||
#define set_temp(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 adm9240_data *data = adm9240_update_device(dev); \
|
||||
long temp = simple_strtoul(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->value = TEMP_TO_REG(temp); \
|
||||
adm9240_write_value(client, reg, data->value); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
}
|
||||
|
||||
set_temp(temp_high, ADM9240_REG_TEMP_HIGH);
|
||||
set_temp(temp_hyst, ADM9240_REG_TEMP_HYST);
|
||||
|
||||
static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
||||
show_temp_high, set_temp_high);
|
||||
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, NULL);
|
||||
|
||||
/* voltage */
|
||||
static ssize_t show_in(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in[nr], nr));
|
||||
}
|
||||
|
||||
static ssize_t show_in_min(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[nr], nr));
|
||||
}
|
||||
|
||||
static ssize_t show_in_max(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[nr], nr));
|
||||
}
|
||||
|
||||
static ssize_t set_in_min(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm9240_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->in_min[nr] = IN_TO_REG(val, nr);
|
||||
adm9240_write_value(client, ADM9240_REG_IN_MIN(nr), data->in_min[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_in_max(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm9240_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->in_max[nr] = IN_TO_REG(val, nr);
|
||||
adm9240_write_value(client, ADM9240_REG_IN_MAX(nr), data->in_max[nr]);
|
||||
up(&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);
|
||||
|
||||
show_in_offset(0);
|
||||
show_in_offset(1);
|
||||
show_in_offset(2);
|
||||
show_in_offset(3);
|
||||
show_in_offset(4);
|
||||
show_in_offset(5);
|
||||
|
||||
/* fans */
|
||||
static ssize_t show_fan(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr],
|
||||
1 << data->fan_div[nr]));
|
||||
}
|
||||
|
||||
static ssize_t show_fan_min(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr],
|
||||
1 << data->fan_div[nr]));
|
||||
}
|
||||
|
||||
static ssize_t show_fan_div(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%d\n", 1 << data->fan_div[nr]);
|
||||
}
|
||||
|
||||
/* write new fan div, callers must hold data->update_lock */
|
||||
static void adm9240_write_fan_div(struct i2c_client *client, int nr,
|
||||
u8 fan_div)
|
||||
{
|
||||
u8 reg, old, shift = (nr + 2) * 2;
|
||||
|
||||
reg = adm9240_read_value(client, ADM9240_REG_VID_FAN_DIV);
|
||||
old = (reg >> shift) & 3;
|
||||
reg &= ~(3 << shift);
|
||||
reg |= (fan_div << shift);
|
||||
adm9240_write_value(client, ADM9240_REG_VID_FAN_DIV, reg);
|
||||
dev_dbg(&client->dev, "fan%d clock divider changed from %u "
|
||||
"to %u\n", nr + 1, 1 << old, 1 << fan_div);
|
||||
}
|
||||
|
||||
/*
|
||||
* set fan speed low limit:
|
||||
*
|
||||
* - value is zero: disable fan speed low limit alarm
|
||||
*
|
||||
* - value is below fan speed measurement range: enable fan speed low
|
||||
* limit alarm to be asserted while fan speed too slow to measure
|
||||
*
|
||||
* - otherwise: select fan clock divider to suit fan speed low limit,
|
||||
* measurement code may adjust registers to ensure fan speed reading
|
||||
*/
|
||||
static ssize_t set_fan_min(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm9240_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
u8 new_div;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (!val) {
|
||||
data->fan_min[nr] = 255;
|
||||
new_div = data->fan_div[nr];
|
||||
|
||||
dev_dbg(&client->dev, "fan%u low limit set disabled\n",
|
||||
nr + 1);
|
||||
|
||||
} else if (val < 1350000 / (8 * 254)) {
|
||||
new_div = 3;
|
||||
data->fan_min[nr] = 254;
|
||||
|
||||
dev_dbg(&client->dev, "fan%u low limit set minimum %u\n",
|
||||
nr + 1, FAN_FROM_REG(254, 1 << new_div));
|
||||
|
||||
} else {
|
||||
unsigned int new_min = 1350000 / val;
|
||||
|
||||
new_div = 0;
|
||||
while (new_min > 192 && new_div < 3) {
|
||||
new_div++;
|
||||
new_min /= 2;
|
||||
}
|
||||
if (!new_min) /* keep > 0 */
|
||||
new_min++;
|
||||
|
||||
data->fan_min[nr] = new_min;
|
||||
|
||||
dev_dbg(&client->dev, "fan%u low limit set fan speed %u\n",
|
||||
nr + 1, FAN_FROM_REG(new_min, 1 << new_div));
|
||||
}
|
||||
|
||||
if (new_div != data->fan_div[nr]) {
|
||||
data->fan_div[nr] = new_div;
|
||||
adm9240_write_fan_div(client, nr, new_div);
|
||||
}
|
||||
adm9240_write_value(client, ADM9240_REG_FAN_MIN(nr),
|
||||
data->fan_min[nr]);
|
||||
|
||||
up(&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##_div (struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
return show_fan_div(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 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##_div, S_IRUGO, \
|
||||
show_fan_##offset##_div, NULL); \
|
||||
static DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \
|
||||
show_fan_##offset##_min, set_fan_##offset##_min);
|
||||
|
||||
show_fan_offset(1);
|
||||
show_fan_offset(2);
|
||||
|
||||
/* alarms */
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%u\n", data->alarms);
|
||||
}
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
|
||||
/* vid */
|
||||
static ssize_t show_vid(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm));
|
||||
}
|
||||
static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL);
|
||||
|
||||
/* analog output */
|
||||
static ssize_t show_aout(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adm9240_data *data = adm9240_update_device(dev);
|
||||
return sprintf(buf, "%d\n", AOUT_FROM_REG(data->aout));
|
||||
}
|
||||
|
||||
static ssize_t set_aout(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm9240_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->aout = AOUT_TO_REG(val);
|
||||
adm9240_write_value(client, ADM9240_REG_ANALOG_OUT, data->aout);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR(aout_output, S_IRUGO | S_IWUSR, show_aout, set_aout);
|
||||
|
||||
/* chassis_clear */
|
||||
static ssize_t chassis_clear(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
unsigned long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
if (val == 1) {
|
||||
adm9240_write_value(client, ADM9240_REG_CHASSIS_CLEAR, 0x80);
|
||||
dev_dbg(&client->dev, "chassis intrusion latch cleared\n");
|
||||
}
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR(chassis_clear, S_IWUSR, NULL, chassis_clear);
|
||||
|
||||
|
||||
/*** sensor chip detect and driver install ***/
|
||||
|
||||
static int adm9240_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct adm9240_data *data;
|
||||
int err = 0;
|
||||
const char *name = "";
|
||||
u8 man_id, die_rev;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct adm9240_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct adm9240_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &adm9240_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
if (kind == 0) {
|
||||
kind = adm9240;
|
||||
}
|
||||
|
||||
if (kind < 0) {
|
||||
|
||||
/* verify chip: reg address should match i2c address */
|
||||
if (adm9240_read_value(new_client, ADM9240_REG_I2C_ADDR)
|
||||
!= address) {
|
||||
dev_err(&adapter->dev, "detect fail: address match, "
|
||||
"0x%02x\n", address);
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* check known chip manufacturer */
|
||||
man_id = adm9240_read_value(new_client, ADM9240_REG_MAN_ID);
|
||||
|
||||
if (man_id == 0x23) {
|
||||
kind = adm9240;
|
||||
} else if (man_id == 0xda) {
|
||||
kind = ds1780;
|
||||
} else if (man_id == 0x01) {
|
||||
kind = lm81;
|
||||
} else {
|
||||
dev_err(&adapter->dev, "detect fail: unknown manuf, "
|
||||
"0x%02x\n", man_id);
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* successful detect, print chip info */
|
||||
die_rev = adm9240_read_value(new_client, ADM9240_REG_DIE_REV);
|
||||
dev_info(&adapter->dev, "found %s revision %u\n",
|
||||
man_id == 0x23 ? "ADM9240" :
|
||||
man_id == 0xda ? "DS1780" : "LM81", die_rev);
|
||||
}
|
||||
|
||||
/* either forced or detected chip kind */
|
||||
if (kind == adm9240) {
|
||||
name = "adm9240";
|
||||
} else if (kind == ds1780) {
|
||||
name = "ds1780";
|
||||
} else if (kind == lm81) {
|
||||
name = "lm81";
|
||||
}
|
||||
|
||||
/* fill in the remaining client fields and attach */
|
||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||
data->type = kind;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
adm9240_init_client(new_client);
|
||||
|
||||
/* populate sysfs filesystem */
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
device_create_file(&new_client->dev, &dev_attr_aout_output);
|
||||
device_create_file(&new_client->dev, &dev_attr_chassis_clear);
|
||||
device_create_file(&new_client->dev, &dev_attr_cpu0_vid);
|
||||
|
||||
return 0;
|
||||
exit_free:
|
||||
kfree(new_client);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int adm9240_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, adm9240_detect);
|
||||
}
|
||||
|
||||
static int adm9240_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, "
|
||||
"client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void adm9240_init_client(struct i2c_client *client)
|
||||
{
|
||||
struct adm9240_data *data = i2c_get_clientdata(client);
|
||||
u8 conf = adm9240_read_value(client, ADM9240_REG_CONFIG);
|
||||
u8 mode = adm9240_read_value(client, ADM9240_REG_TEMP_CONF) & 3;
|
||||
|
||||
data->vrm = i2c_which_vrm(); /* need this to report vid as mV */
|
||||
|
||||
dev_info(&client->dev, "Using VRM: %d.%d\n", data->vrm / 10,
|
||||
data->vrm % 10);
|
||||
|
||||
if (conf & 1) { /* measurement cycle running: report state */
|
||||
|
||||
dev_info(&client->dev, "status: config 0x%02x mode %u\n",
|
||||
conf, mode);
|
||||
|
||||
} else { /* cold start: open limits before starting chip */
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 6; i++)
|
||||
{
|
||||
adm9240_write_value(client,
|
||||
ADM9240_REG_IN_MIN(i), 0);
|
||||
adm9240_write_value(client,
|
||||
ADM9240_REG_IN_MAX(i), 255);
|
||||
}
|
||||
adm9240_write_value(client, ADM9240_REG_FAN_MIN(0), 255);
|
||||
adm9240_write_value(client, ADM9240_REG_FAN_MIN(1), 255);
|
||||
adm9240_write_value(client, ADM9240_REG_TEMP_HIGH, 127);
|
||||
adm9240_write_value(client, ADM9240_REG_TEMP_HYST, 127);
|
||||
|
||||
/* start measurement cycle */
|
||||
adm9240_write_value(client, ADM9240_REG_CONFIG, 1);
|
||||
|
||||
dev_info(&client->dev, "cold start: config was 0x%02x "
|
||||
"mode %u\n", conf, mode);
|
||||
}
|
||||
}
|
||||
|
||||
static struct adm9240_data *adm9240_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct adm9240_data *data = i2c_get_clientdata(client);
|
||||
int i;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
/* minimum measurement cycle: 1.75 seconds */
|
||||
if (time_after(jiffies, data->last_updated_measure + (HZ * 7 / 4))
|
||||
|| !data->valid) {
|
||||
|
||||
for (i = 0; i < 6; i++) /* read voltages */
|
||||
{
|
||||
data->in[i] = adm9240_read_value(client,
|
||||
ADM9240_REG_IN(i));
|
||||
}
|
||||
data->alarms = adm9240_read_value(client,
|
||||
ADM9240_REG_INT(0)) |
|
||||
adm9240_read_value(client,
|
||||
ADM9240_REG_INT(1)) << 8;
|
||||
|
||||
/* read temperature: assume temperature changes less than
|
||||
* 0.5'C per two measurement cycles thus ignore possible
|
||||
* but unlikely aliasing error on lsb reading. --Grant */
|
||||
data->temp = ((adm9240_read_value(client,
|
||||
ADM9240_REG_TEMP) << 8) |
|
||||
adm9240_read_value(client,
|
||||
ADM9240_REG_TEMP_CONF)) / 128;
|
||||
|
||||
for (i = 0; i < 2; i++) /* read fans */
|
||||
{
|
||||
data->fan[i] = adm9240_read_value(client,
|
||||
ADM9240_REG_FAN(i));
|
||||
|
||||
/* adjust fan clock divider on overflow */
|
||||
if (data->valid && data->fan[i] == 255 &&
|
||||
data->fan_div[i] < 3) {
|
||||
|
||||
adm9240_write_fan_div(client, i,
|
||||
++data->fan_div[i]);
|
||||
|
||||
/* adjust fan_min if active, but not to 0 */
|
||||
if (data->fan_min[i] < 255 &&
|
||||
data->fan_min[i] >= 2)
|
||||
data->fan_min[i] /= 2;
|
||||
}
|
||||
}
|
||||
data->last_updated_measure = jiffies;
|
||||
}
|
||||
|
||||
/* minimum config reading cycle: 300 seconds */
|
||||
if (time_after(jiffies, data->last_updated_config + (HZ * 300))
|
||||
|| !data->valid) {
|
||||
|
||||
for (i = 0; i < 6; i++)
|
||||
{
|
||||
data->in_min[i] = adm9240_read_value(client,
|
||||
ADM9240_REG_IN_MIN(i));
|
||||
data->in_max[i] = adm9240_read_value(client,
|
||||
ADM9240_REG_IN_MAX(i));
|
||||
}
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
data->fan_min[i] = adm9240_read_value(client,
|
||||
ADM9240_REG_FAN_MIN(i));
|
||||
}
|
||||
data->temp_high = adm9240_read_value(client,
|
||||
ADM9240_REG_TEMP_HIGH);
|
||||
data->temp_hyst = adm9240_read_value(client,
|
||||
ADM9240_REG_TEMP_HYST);
|
||||
|
||||
/* read fan divs and 5-bit VID */
|
||||
i = adm9240_read_value(client, ADM9240_REG_VID_FAN_DIV);
|
||||
data->fan_div[0] = (i >> 4) & 3;
|
||||
data->fan_div[1] = (i >> 6) & 3;
|
||||
data->vid = i & 0x0f;
|
||||
data->vid |= (adm9240_read_value(client,
|
||||
ADM9240_REG_VID4) & 1) << 4;
|
||||
/* read analog out */
|
||||
data->aout = adm9240_read_value(client,
|
||||
ADM9240_REG_ANALOG_OUT);
|
||||
|
||||
data->last_updated_config = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
up(&data->update_lock);
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_adm9240_init(void)
|
||||
{
|
||||
return i2c_add_driver(&adm9240_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_adm9240_exit(void)
|
||||
{
|
||||
i2c_del_driver(&adm9240_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Michiel Rook <michiel@grendelproject.nl>, "
|
||||
"Grant Coady <gcoady@gmail.com> and others");
|
||||
MODULE_DESCRIPTION("ADM9240/DS1780/LM81 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_adm9240_init);
|
||||
module_exit(sensors_adm9240_exit);
|
||||
|
||||
1065
drivers/hwmon/asb100.c
Normal file
1065
drivers/hwmon/asb100.c
Normal file
File diff suppressed because it is too large
Load diff
361
drivers/hwmon/atxp1.c
Normal file
361
drivers/hwmon/atxp1.c
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
atxp1.c - kernel module for setting CPU VID and general purpose
|
||||
I/Os using the Attansic ATXP1 chip.
|
||||
|
||||
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/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-sensor.h>
|
||||
#include <linux/i2c-vid.h>
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("System voltages control via Attansic ATXP1");
|
||||
MODULE_VERSION("0.6.2");
|
||||
MODULE_AUTHOR("Sebastian Witt <se.witt@gmx.net>");
|
||||
|
||||
#define ATXP1_VID 0x00
|
||||
#define ATXP1_CVID 0x01
|
||||
#define ATXP1_GPIO1 0x06
|
||||
#define ATXP1_GPIO2 0x0a
|
||||
#define ATXP1_VIDENA 0x20
|
||||
#define ATXP1_VIDMASK 0x1f
|
||||
#define ATXP1_GPIO1MASK 0x0f
|
||||
|
||||
static unsigned short normal_i2c[] = { 0x37, 0x4e, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
SENSORS_INSMOD_1(atxp1);
|
||||
|
||||
static int atxp1_attach_adapter(struct i2c_adapter * adapter);
|
||||
static int atxp1_detach_client(struct i2c_client * client);
|
||||
static struct atxp1_data * atxp1_update_device(struct device *dev);
|
||||
static int atxp1_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
|
||||
static struct i2c_driver atxp1_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "atxp1",
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = atxp1_attach_adapter,
|
||||
.detach_client = atxp1_detach_client,
|
||||
};
|
||||
|
||||
struct atxp1_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
unsigned long last_updated;
|
||||
u8 valid;
|
||||
struct {
|
||||
u8 vid; /* VID output register */
|
||||
u8 cpu_vid; /* VID input from CPU */
|
||||
u8 gpio1; /* General purpose I/O register 1 */
|
||||
u8 gpio2; /* General purpose I/O register 2 */
|
||||
} reg;
|
||||
u8 vrm; /* Detected CPU VRM */
|
||||
};
|
||||
|
||||
static struct atxp1_data * atxp1_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client;
|
||||
struct atxp1_data *data;
|
||||
|
||||
client = to_i2c_client(dev);
|
||||
data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if ((jiffies - data->last_updated > HZ) ||
|
||||
(jiffies < data->last_updated) ||
|
||||
!data->valid) {
|
||||
|
||||
/* Update local register data */
|
||||
data->reg.vid = i2c_smbus_read_byte_data(client, ATXP1_VID);
|
||||
data->reg.cpu_vid = i2c_smbus_read_byte_data(client, ATXP1_CVID);
|
||||
data->reg.gpio1 = i2c_smbus_read_byte_data(client, ATXP1_GPIO1);
|
||||
data->reg.gpio2 = i2c_smbus_read_byte_data(client, ATXP1_GPIO2);
|
||||
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return(data);
|
||||
}
|
||||
|
||||
/* sys file functions for cpu0_vid */
|
||||
static ssize_t atxp1_showvcore(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int size;
|
||||
struct atxp1_data *data;
|
||||
|
||||
data = atxp1_update_device(dev);
|
||||
|
||||
size = sprintf(buf, "%d\n", vid_from_reg(data->reg.vid & ATXP1_VIDMASK, data->vrm));
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t atxp1_storevcore(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct atxp1_data *data;
|
||||
struct i2c_client *client;
|
||||
char vid;
|
||||
char cvid;
|
||||
unsigned int vcore;
|
||||
|
||||
client = to_i2c_client(dev);
|
||||
data = atxp1_update_device(dev);
|
||||
|
||||
vcore = simple_strtoul(buf, NULL, 10);
|
||||
vcore /= 25;
|
||||
vcore *= 25;
|
||||
|
||||
/* Calculate VID */
|
||||
vid = vid_to_reg(vcore, data->vrm);
|
||||
|
||||
if (vid < 0) {
|
||||
dev_err(dev, "VID calculation failed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If output enabled, use control register value. Otherwise original CPU VID */
|
||||
if (data->reg.vid & ATXP1_VIDENA)
|
||||
cvid = data->reg.vid & ATXP1_VIDMASK;
|
||||
else
|
||||
cvid = data->reg.cpu_vid;
|
||||
|
||||
/* Nothing changed, aborting */
|
||||
if (vid == cvid)
|
||||
return count;
|
||||
|
||||
dev_dbg(dev, "Setting VCore to %d mV (0x%02x)\n", vcore, vid);
|
||||
|
||||
/* Write every 25 mV step to increase stability */
|
||||
if (cvid > vid) {
|
||||
for (; cvid >= vid; cvid--) {
|
||||
i2c_smbus_write_byte_data(client, ATXP1_VID, cvid | ATXP1_VIDENA);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (; cvid <= vid; cvid++) {
|
||||
i2c_smbus_write_byte_data(client, ATXP1_VID, cvid | ATXP1_VIDENA);
|
||||
}
|
||||
}
|
||||
|
||||
data->valid = 0;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* CPU core reference voltage
|
||||
unit: millivolt
|
||||
*/
|
||||
static DEVICE_ATTR(cpu0_vid, S_IRUGO | S_IWUSR, atxp1_showvcore, atxp1_storevcore);
|
||||
|
||||
/* sys file functions for GPIO1 */
|
||||
static ssize_t atxp1_showgpio1(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int size;
|
||||
struct atxp1_data *data;
|
||||
|
||||
data = atxp1_update_device(dev);
|
||||
|
||||
size = sprintf(buf, "0x%02x\n", data->reg.gpio1 & ATXP1_GPIO1MASK);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t atxp1_storegpio1(struct device *dev, struct device_attribute *attr, const char*buf, size_t count)
|
||||
{
|
||||
struct atxp1_data *data;
|
||||
struct i2c_client *client;
|
||||
unsigned int value;
|
||||
|
||||
client = to_i2c_client(dev);
|
||||
data = atxp1_update_device(dev);
|
||||
|
||||
value = simple_strtoul(buf, NULL, 16);
|
||||
|
||||
value &= ATXP1_GPIO1MASK;
|
||||
|
||||
if (value != (data->reg.gpio1 & ATXP1_GPIO1MASK)) {
|
||||
dev_info(dev, "Writing 0x%x to GPIO1.\n", value);
|
||||
|
||||
i2c_smbus_write_byte_data(client, ATXP1_GPIO1, value);
|
||||
|
||||
data->valid = 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* GPIO1 data register
|
||||
unit: Four bit as hex (e.g. 0x0f)
|
||||
*/
|
||||
static DEVICE_ATTR(gpio1, S_IRUGO | S_IWUSR, atxp1_showgpio1, atxp1_storegpio1);
|
||||
|
||||
/* sys file functions for GPIO2 */
|
||||
static ssize_t atxp1_showgpio2(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int size;
|
||||
struct atxp1_data *data;
|
||||
|
||||
data = atxp1_update_device(dev);
|
||||
|
||||
size = sprintf(buf, "0x%02x\n", data->reg.gpio2);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t atxp1_storegpio2(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct atxp1_data *data;
|
||||
struct i2c_client *client;
|
||||
unsigned int value;
|
||||
|
||||
client = to_i2c_client(dev);
|
||||
data = atxp1_update_device(dev);
|
||||
|
||||
value = simple_strtoul(buf, NULL, 16) & 0xff;
|
||||
|
||||
if (value != data->reg.gpio2) {
|
||||
dev_info(dev, "Writing 0x%x to GPIO1.\n", value);
|
||||
|
||||
i2c_smbus_write_byte_data(client, ATXP1_GPIO2, value);
|
||||
|
||||
data->valid = 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* GPIO2 data register
|
||||
unit: Eight bit as hex (e.g. 0xff)
|
||||
*/
|
||||
static DEVICE_ATTR(gpio2, S_IRUGO | S_IWUSR, atxp1_showgpio2, atxp1_storegpio2);
|
||||
|
||||
|
||||
static int atxp1_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
return i2c_detect(adapter, &addr_data, &atxp1_detect);
|
||||
};
|
||||
|
||||
static int atxp1_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client * new_client;
|
||||
struct atxp1_data * data;
|
||||
int err = 0;
|
||||
u8 temp;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct atxp1_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
memset(data, 0, sizeof(struct atxp1_data));
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &atxp1_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Detect ATXP1, checking if vendor ID registers are all zero */
|
||||
if (!((i2c_smbus_read_byte_data(new_client, 0x3e) == 0) &&
|
||||
(i2c_smbus_read_byte_data(new_client, 0x3f) == 0) &&
|
||||
(i2c_smbus_read_byte_data(new_client, 0xfe) == 0) &&
|
||||
(i2c_smbus_read_byte_data(new_client, 0xff) == 0) )) {
|
||||
|
||||
/* No vendor ID, now checking if registers 0x10,0x11 (non-existent)
|
||||
* showing the same as register 0x00 */
|
||||
temp = i2c_smbus_read_byte_data(new_client, 0x00);
|
||||
|
||||
if (!((i2c_smbus_read_byte_data(new_client, 0x10) == temp) &&
|
||||
(i2c_smbus_read_byte_data(new_client, 0x11) == temp) ))
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Get VRM */
|
||||
data->vrm = i2c_which_vrm();
|
||||
|
||||
if ((data->vrm != 90) && (data->vrm != 91)) {
|
||||
dev_err(&new_client->dev, "Not supporting VRM %d.%d\n",
|
||||
data->vrm / 10, data->vrm % 10);
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
strncpy(new_client->name, "atxp1", I2C_NAME_SIZE);
|
||||
|
||||
data->valid = 0;
|
||||
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
err = i2c_attach_client(new_client);
|
||||
|
||||
if (err)
|
||||
{
|
||||
dev_err(&new_client->dev, "Attach client error.\n");
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
device_create_file(&new_client->dev, &dev_attr_gpio1);
|
||||
device_create_file(&new_client->dev, &dev_attr_gpio2);
|
||||
device_create_file(&new_client->dev, &dev_attr_cpu0_vid);
|
||||
|
||||
dev_info(&new_client->dev, "Using VRM: %d.%d\n",
|
||||
data->vrm / 10, data->vrm % 10);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
};
|
||||
|
||||
static int atxp1_detach_client(struct i2c_client * client)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = i2c_detach_client(client);
|
||||
|
||||
if (err)
|
||||
dev_err(&client->dev, "Failed to detach client.\n");
|
||||
else
|
||||
kfree(i2c_get_clientdata(client));
|
||||
|
||||
return err;
|
||||
};
|
||||
|
||||
static int __init atxp1_init(void)
|
||||
{
|
||||
return i2c_add_driver(&atxp1_driver);
|
||||
};
|
||||
|
||||
static void __exit atxp1_exit(void)
|
||||
{
|
||||
i2c_del_driver(&atxp1_driver);
|
||||
};
|
||||
|
||||
module_init(atxp1_init);
|
||||
module_exit(atxp1_exit);
|
||||
341
drivers/hwmon/ds1621.c
Normal file
341
drivers/hwmon/ds1621.c
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
ds1621.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Christian W. Zuckschwerdt <zany@triq.net> 2000-11-23
|
||||
based on lm75.c by Frodo Looijaard <frodol@dds.nl>
|
||||
Ported to Linux 2.6 by Aurelien Jarno <aurelien@aurel32.net> with
|
||||
the help of 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
|
||||
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/i2c-sensor.h>
|
||||
#include "lm75.h"
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
|
||||
0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_1(ds1621);
|
||||
static int polarity = -1;
|
||||
module_param(polarity, int, 0);
|
||||
MODULE_PARM_DESC(polarity, "Output's polarity: 0 = active high, 1 = active low");
|
||||
|
||||
/* Many DS1621 constants specified below */
|
||||
/* Config register used for detection */
|
||||
/* 7 6 5 4 3 2 1 0 */
|
||||
/* |Done|THF |TLF |NVB | X | X |POL |1SHOT| */
|
||||
#define DS1621_REG_CONFIG_NVB 0x10
|
||||
#define DS1621_REG_CONFIG_POLARITY 0x02
|
||||
#define DS1621_REG_CONFIG_1SHOT 0x01
|
||||
#define DS1621_REG_CONFIG_DONE 0x80
|
||||
|
||||
/* The DS1621 registers */
|
||||
#define DS1621_REG_TEMP 0xAA /* word, RO */
|
||||
#define DS1621_REG_TEMP_MIN 0xA1 /* word, RW */
|
||||
#define DS1621_REG_TEMP_MAX 0xA2 /* word, RW */
|
||||
#define DS1621_REG_CONF 0xAC /* byte, RW */
|
||||
#define DS1621_COM_START 0xEE /* no data */
|
||||
#define DS1621_COM_STOP 0x22 /* no data */
|
||||
|
||||
/* The DS1621 configuration register */
|
||||
#define DS1621_ALARM_TEMP_HIGH 0x40
|
||||
#define DS1621_ALARM_TEMP_LOW 0x20
|
||||
|
||||
/* Conversions. Rounding and limit checking is only done on the TO_REG
|
||||
variants. Note that you should be a bit careful with which arguments
|
||||
these macros are called: arguments may be evaluated more than once.
|
||||
Fixing this is just not worth it. */
|
||||
#define ALARMS_FROM_REG(val) ((val) & \
|
||||
(DS1621_ALARM_TEMP_HIGH | DS1621_ALARM_TEMP_LOW))
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct ds1621_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid; /* !=0 if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
||||
u16 temp, temp_min, temp_max; /* Register values, word */
|
||||
u8 conf; /* Register encoding, combined */
|
||||
};
|
||||
|
||||
static int ds1621_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int ds1621_detect(struct i2c_adapter *adapter, int address,
|
||||
int kind);
|
||||
static void ds1621_init_client(struct i2c_client *client);
|
||||
static int ds1621_detach_client(struct i2c_client *client);
|
||||
static struct ds1621_data *ds1621_update_client(struct device *dev);
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver ds1621_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "ds1621",
|
||||
.id = I2C_DRIVERID_DS1621,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = ds1621_attach_adapter,
|
||||
.detach_client = ds1621_detach_client,
|
||||
};
|
||||
|
||||
/* All registers are word-sized, except for the configuration register.
|
||||
DS1621 uses a high-byte first convention, which is exactly opposite to
|
||||
the usual practice. */
|
||||
static int ds1621_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
if (reg == DS1621_REG_CONF)
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
else
|
||||
return swab16(i2c_smbus_read_word_data(client, reg));
|
||||
}
|
||||
|
||||
/* All registers are word-sized, except for the configuration register.
|
||||
DS1621 uses a high-byte first convention, which is exactly opposite to
|
||||
the usual practice. */
|
||||
static int ds1621_write_value(struct i2c_client *client, u8 reg, u16 value)
|
||||
{
|
||||
if (reg == DS1621_REG_CONF)
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
else
|
||||
return i2c_smbus_write_word_data(client, reg, swab16(value));
|
||||
}
|
||||
|
||||
static void ds1621_init_client(struct i2c_client *client)
|
||||
{
|
||||
int reg = ds1621_read_value(client, DS1621_REG_CONF);
|
||||
/* switch to continuous conversion mode */
|
||||
reg &= ~ DS1621_REG_CONFIG_1SHOT;
|
||||
|
||||
/* setup output polarity */
|
||||
if (polarity == 0)
|
||||
reg &= ~DS1621_REG_CONFIG_POLARITY;
|
||||
else if (polarity == 1)
|
||||
reg |= DS1621_REG_CONFIG_POLARITY;
|
||||
|
||||
ds1621_write_value(client, DS1621_REG_CONF, reg);
|
||||
|
||||
/* start conversion */
|
||||
i2c_smbus_write_byte(client, DS1621_COM_START);
|
||||
}
|
||||
|
||||
#define show(value) \
|
||||
static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct ds1621_data *data = ds1621_update_client(dev); \
|
||||
return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->value)); \
|
||||
}
|
||||
|
||||
show(temp);
|
||||
show(temp_min);
|
||||
show(temp_max);
|
||||
|
||||
#define set_temp(suffix, value, reg) \
|
||||
static ssize_t set_temp_##suffix(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct ds1621_data *data = ds1621_update_client(dev); \
|
||||
u16 val = LM75_TEMP_TO_REG(simple_strtoul(buf, NULL, 10)); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->value = val; \
|
||||
ds1621_write_value(client, reg, data->value); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
}
|
||||
|
||||
set_temp(min, temp_min, DS1621_REG_TEMP_MIN);
|
||||
set_temp(max, temp_max, DS1621_REG_TEMP_MAX);
|
||||
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct ds1621_data *data = ds1621_update_client(dev);
|
||||
return sprintf(buf, "%d\n", ALARMS_FROM_REG(data->conf));
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO , show_temp, NULL);
|
||||
static DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO , show_temp_min, set_temp_min);
|
||||
static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max);
|
||||
|
||||
|
||||
static int ds1621_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
return i2c_detect(adapter, &addr_data, ds1621_detect);
|
||||
}
|
||||
|
||||
/* This function is called by i2c_detect */
|
||||
int ds1621_detect(struct i2c_adapter *adapter, int address,
|
||||
int kind)
|
||||
{
|
||||
int conf, temp;
|
||||
struct i2c_client *new_client;
|
||||
struct ds1621_data *data;
|
||||
int err = 0;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
|
||||
| I2C_FUNC_SMBUS_WORD_DATA
|
||||
| I2C_FUNC_SMBUS_WRITE_BYTE))
|
||||
goto exit;
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
client structure, even though we cannot fill it completely yet.
|
||||
But it allows us to access ds1621_{read,write}_value. */
|
||||
if (!(data = kmalloc(sizeof(struct ds1621_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct ds1621_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &ds1621_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
|
||||
/* Now, we do the remaining detection. It is lousy. */
|
||||
if (kind < 0) {
|
||||
/* The NVB bit should be low if no EEPROM write has been
|
||||
requested during the latest 10ms, which is highly
|
||||
improbable in our case. */
|
||||
conf = ds1621_read_value(new_client, DS1621_REG_CONF);
|
||||
if (conf & DS1621_REG_CONFIG_NVB)
|
||||
goto exit_free;
|
||||
/* The 7 lowest bits of a temperature should always be 0. */
|
||||
temp = ds1621_read_value(new_client, DS1621_REG_TEMP);
|
||||
if (temp & 0x007f)
|
||||
goto exit_free;
|
||||
temp = ds1621_read_value(new_client, DS1621_REG_TEMP_MIN);
|
||||
if (temp & 0x007f)
|
||||
goto exit_free;
|
||||
temp = ds1621_read_value(new_client, DS1621_REG_TEMP_MAX);
|
||||
if (temp & 0x007f)
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Determine the chip type - only one kind supported! */
|
||||
if (kind <= 0)
|
||||
kind = ds1621;
|
||||
|
||||
/* Fill in remaining client fields and put it into the global list */
|
||||
strlcpy(new_client->name, "ds1621", I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Initialize the DS1621 chip */
|
||||
ds1621_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
|
||||
return 0;
|
||||
|
||||
/* OK, this is not exactly good programming practice, usually. But it is
|
||||
very code-efficient in this case. */
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ds1621_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, "
|
||||
"client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct ds1621_data *ds1621_update_client(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct ds1621_data *data = i2c_get_clientdata(client);
|
||||
u8 new_conf;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
|
||||
dev_dbg(&client->dev, "Starting ds1621 update\n");
|
||||
|
||||
data->conf = ds1621_read_value(client, DS1621_REG_CONF);
|
||||
|
||||
data->temp = ds1621_read_value(client, DS1621_REG_TEMP);
|
||||
|
||||
data->temp_min = ds1621_read_value(client,
|
||||
DS1621_REG_TEMP_MIN);
|
||||
data->temp_max = ds1621_read_value(client,
|
||||
DS1621_REG_TEMP_MAX);
|
||||
|
||||
/* reset alarms if necessary */
|
||||
new_conf = data->conf;
|
||||
if (data->temp < data->temp_min)
|
||||
new_conf &= ~DS1621_ALARM_TEMP_LOW;
|
||||
if (data->temp > data->temp_max)
|
||||
new_conf &= ~DS1621_ALARM_TEMP_HIGH;
|
||||
if (data->conf != new_conf)
|
||||
ds1621_write_value(client, DS1621_REG_CONF,
|
||||
new_conf);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init ds1621_init(void)
|
||||
{
|
||||
return i2c_add_driver(&ds1621_driver);
|
||||
}
|
||||
|
||||
static void __exit ds1621_exit(void)
|
||||
{
|
||||
i2c_del_driver(&ds1621_driver);
|
||||
}
|
||||
|
||||
|
||||
MODULE_AUTHOR("Christian W. Zuckschwerdt <zany@triq.net>");
|
||||
MODULE_DESCRIPTION("DS1621 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(ds1621_init);
|
||||
module_exit(ds1621_exit);
|
||||
691
drivers/hwmon/fscher.c
Normal file
691
drivers/hwmon/fscher.c
Normal file
|
|
@ -0,0 +1,691 @@
|
|||
/*
|
||||
* fscher.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
* monitoring
|
||||
* Copyright (C) 2003, 2004 Reinhard Nissl <rnissl@gmx.de>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* fujitsu siemens hermes chip,
|
||||
* module based on fscpos.c
|
||||
* Copyright (C) 2000 Hermann Jung <hej@odn.de>
|
||||
* Copyright (C) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
|
||||
* and Philip Edelbrock <phil@netroedge.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-sensor.h>
|
||||
|
||||
/*
|
||||
* Addresses to scan
|
||||
*/
|
||||
|
||||
static unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/*
|
||||
* Insmod parameters
|
||||
*/
|
||||
|
||||
SENSORS_INSMOD_1(fscher);
|
||||
|
||||
/*
|
||||
* The FSCHER registers
|
||||
*/
|
||||
|
||||
/* chip identification */
|
||||
#define FSCHER_REG_IDENT_0 0x00
|
||||
#define FSCHER_REG_IDENT_1 0x01
|
||||
#define FSCHER_REG_IDENT_2 0x02
|
||||
#define FSCHER_REG_REVISION 0x03
|
||||
|
||||
/* global control and status */
|
||||
#define FSCHER_REG_EVENT_STATE 0x04
|
||||
#define FSCHER_REG_CONTROL 0x05
|
||||
|
||||
/* watchdog */
|
||||
#define FSCHER_REG_WDOG_PRESET 0x28
|
||||
#define FSCHER_REG_WDOG_STATE 0x23
|
||||
#define FSCHER_REG_WDOG_CONTROL 0x21
|
||||
|
||||
/* fan 0 */
|
||||
#define FSCHER_REG_FAN0_MIN 0x55
|
||||
#define FSCHER_REG_FAN0_ACT 0x0e
|
||||
#define FSCHER_REG_FAN0_STATE 0x0d
|
||||
#define FSCHER_REG_FAN0_RIPPLE 0x0f
|
||||
|
||||
/* fan 1 */
|
||||
#define FSCHER_REG_FAN1_MIN 0x65
|
||||
#define FSCHER_REG_FAN1_ACT 0x6b
|
||||
#define FSCHER_REG_FAN1_STATE 0x62
|
||||
#define FSCHER_REG_FAN1_RIPPLE 0x6f
|
||||
|
||||
/* fan 2 */
|
||||
#define FSCHER_REG_FAN2_MIN 0xb5
|
||||
#define FSCHER_REG_FAN2_ACT 0xbb
|
||||
#define FSCHER_REG_FAN2_STATE 0xb2
|
||||
#define FSCHER_REG_FAN2_RIPPLE 0xbf
|
||||
|
||||
/* voltage supervision */
|
||||
#define FSCHER_REG_VOLT_12 0x45
|
||||
#define FSCHER_REG_VOLT_5 0x42
|
||||
#define FSCHER_REG_VOLT_BATT 0x48
|
||||
|
||||
/* temperature 0 */
|
||||
#define FSCHER_REG_TEMP0_ACT 0x64
|
||||
#define FSCHER_REG_TEMP0_STATE 0x71
|
||||
|
||||
/* temperature 1 */
|
||||
#define FSCHER_REG_TEMP1_ACT 0x32
|
||||
#define FSCHER_REG_TEMP1_STATE 0x81
|
||||
|
||||
/* temperature 2 */
|
||||
#define FSCHER_REG_TEMP2_ACT 0x35
|
||||
#define FSCHER_REG_TEMP2_STATE 0x91
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
*/
|
||||
|
||||
static int fscher_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int fscher_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static int fscher_detach_client(struct i2c_client *client);
|
||||
static struct fscher_data *fscher_update_device(struct device *dev);
|
||||
static void fscher_init_client(struct i2c_client *client);
|
||||
|
||||
static int fscher_read_value(struct i2c_client *client, u8 reg);
|
||||
static int fscher_write_value(struct i2c_client *client, u8 reg, u8 value);
|
||||
|
||||
/*
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
|
||||
static struct i2c_driver fscher_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "fscher",
|
||||
.id = I2C_DRIVERID_FSCHER,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = fscher_attach_adapter,
|
||||
.detach_client = fscher_detach_client,
|
||||
};
|
||||
|
||||
/*
|
||||
* Client data (each client gets its own)
|
||||
*/
|
||||
|
||||
struct fscher_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
|
||||
/* register values */
|
||||
u8 revision; /* revision of chip */
|
||||
u8 global_event; /* global event status */
|
||||
u8 global_control; /* global control register */
|
||||
u8 watchdog[3]; /* watchdog */
|
||||
u8 volt[3]; /* 12, 5, battery voltage */
|
||||
u8 temp_act[3]; /* temperature */
|
||||
u8 temp_status[3]; /* status of sensor */
|
||||
u8 fan_act[3]; /* fans revolutions per second */
|
||||
u8 fan_status[3]; /* fan status */
|
||||
u8 fan_min[3]; /* fan min value for rps */
|
||||
u8 fan_ripple[3]; /* divider for rps */
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
#define sysfs_r(kind, sub, offset, reg) \
|
||||
static ssize_t show_##kind##sub (struct fscher_data *, char *, int); \
|
||||
static ssize_t show_##kind##offset##sub (struct device *, struct device_attribute *attr, char *); \
|
||||
static ssize_t show_##kind##offset##sub (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct fscher_data *data = fscher_update_device(dev); \
|
||||
return show_##kind##sub(data, buf, (offset)); \
|
||||
}
|
||||
|
||||
#define sysfs_w(kind, sub, offset, reg) \
|
||||
static ssize_t set_##kind##sub (struct i2c_client *, struct fscher_data *, const char *, size_t, int, int); \
|
||||
static ssize_t set_##kind##offset##sub (struct device *, struct device_attribute *attr, const char *, size_t); \
|
||||
static ssize_t set_##kind##offset##sub (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct fscher_data *data = i2c_get_clientdata(client); \
|
||||
return set_##kind##sub(client, data, buf, count, (offset), reg); \
|
||||
}
|
||||
|
||||
#define sysfs_rw_n(kind, sub, offset, reg) \
|
||||
sysfs_r(kind, sub, offset, reg) \
|
||||
sysfs_w(kind, sub, offset, reg) \
|
||||
static DEVICE_ATTR(kind##offset##sub, S_IRUGO | S_IWUSR, show_##kind##offset##sub, set_##kind##offset##sub);
|
||||
|
||||
#define sysfs_rw(kind, sub, reg) \
|
||||
sysfs_r(kind, sub, 0, reg) \
|
||||
sysfs_w(kind, sub, 0, reg) \
|
||||
static DEVICE_ATTR(kind##sub, S_IRUGO | S_IWUSR, show_##kind##0##sub, set_##kind##0##sub);
|
||||
|
||||
#define sysfs_ro_n(kind, sub, offset, reg) \
|
||||
sysfs_r(kind, sub, offset, reg) \
|
||||
static DEVICE_ATTR(kind##offset##sub, S_IRUGO, show_##kind##offset##sub, NULL);
|
||||
|
||||
#define sysfs_ro(kind, sub, reg) \
|
||||
sysfs_r(kind, sub, 0, reg) \
|
||||
static DEVICE_ATTR(kind, S_IRUGO, show_##kind##0##sub, NULL);
|
||||
|
||||
#define sysfs_fan(offset, reg_status, reg_min, reg_ripple, reg_act) \
|
||||
sysfs_rw_n(pwm, , offset, reg_min) \
|
||||
sysfs_rw_n(fan, _status, offset, reg_status) \
|
||||
sysfs_rw_n(fan, _div , offset, reg_ripple) \
|
||||
sysfs_ro_n(fan, _input , offset, reg_act)
|
||||
|
||||
#define sysfs_temp(offset, reg_status, reg_act) \
|
||||
sysfs_rw_n(temp, _status, offset, reg_status) \
|
||||
sysfs_ro_n(temp, _input , offset, reg_act)
|
||||
|
||||
#define sysfs_in(offset, reg_act) \
|
||||
sysfs_ro_n(in, _input, offset, reg_act)
|
||||
|
||||
#define sysfs_revision(reg_revision) \
|
||||
sysfs_ro(revision, , reg_revision)
|
||||
|
||||
#define sysfs_alarms(reg_events) \
|
||||
sysfs_ro(alarms, , reg_events)
|
||||
|
||||
#define sysfs_control(reg_control) \
|
||||
sysfs_rw(control, , reg_control)
|
||||
|
||||
#define sysfs_watchdog(reg_control, reg_status, reg_preset) \
|
||||
sysfs_rw(watchdog, _control, reg_control) \
|
||||
sysfs_rw(watchdog, _status , reg_status) \
|
||||
sysfs_rw(watchdog, _preset , reg_preset)
|
||||
|
||||
sysfs_fan(1, FSCHER_REG_FAN0_STATE, FSCHER_REG_FAN0_MIN,
|
||||
FSCHER_REG_FAN0_RIPPLE, FSCHER_REG_FAN0_ACT)
|
||||
sysfs_fan(2, FSCHER_REG_FAN1_STATE, FSCHER_REG_FAN1_MIN,
|
||||
FSCHER_REG_FAN1_RIPPLE, FSCHER_REG_FAN1_ACT)
|
||||
sysfs_fan(3, FSCHER_REG_FAN2_STATE, FSCHER_REG_FAN2_MIN,
|
||||
FSCHER_REG_FAN2_RIPPLE, FSCHER_REG_FAN2_ACT)
|
||||
|
||||
sysfs_temp(1, FSCHER_REG_TEMP0_STATE, FSCHER_REG_TEMP0_ACT)
|
||||
sysfs_temp(2, FSCHER_REG_TEMP1_STATE, FSCHER_REG_TEMP1_ACT)
|
||||
sysfs_temp(3, FSCHER_REG_TEMP2_STATE, FSCHER_REG_TEMP2_ACT)
|
||||
|
||||
sysfs_in(0, FSCHER_REG_VOLT_12)
|
||||
sysfs_in(1, FSCHER_REG_VOLT_5)
|
||||
sysfs_in(2, FSCHER_REG_VOLT_BATT)
|
||||
|
||||
sysfs_revision(FSCHER_REG_REVISION)
|
||||
sysfs_alarms(FSCHER_REG_EVENTS)
|
||||
sysfs_control(FSCHER_REG_CONTROL)
|
||||
sysfs_watchdog(FSCHER_REG_WDOG_CONTROL, FSCHER_REG_WDOG_STATE, FSCHER_REG_WDOG_PRESET)
|
||||
|
||||
#define device_create_file_fan(client, offset) \
|
||||
do { \
|
||||
device_create_file(&client->dev, &dev_attr_fan##offset##_status); \
|
||||
device_create_file(&client->dev, &dev_attr_pwm##offset); \
|
||||
device_create_file(&client->dev, &dev_attr_fan##offset##_div); \
|
||||
device_create_file(&client->dev, &dev_attr_fan##offset##_input); \
|
||||
} while (0)
|
||||
|
||||
#define device_create_file_temp(client, offset) \
|
||||
do { \
|
||||
device_create_file(&client->dev, &dev_attr_temp##offset##_status); \
|
||||
device_create_file(&client->dev, &dev_attr_temp##offset##_input); \
|
||||
} while (0)
|
||||
|
||||
#define device_create_file_in(client, offset) \
|
||||
do { \
|
||||
device_create_file(&client->dev, &dev_attr_in##offset##_input); \
|
||||
} while (0)
|
||||
|
||||
#define device_create_file_revision(client) \
|
||||
do { \
|
||||
device_create_file(&client->dev, &dev_attr_revision); \
|
||||
} while (0)
|
||||
|
||||
#define device_create_file_alarms(client) \
|
||||
do { \
|
||||
device_create_file(&client->dev, &dev_attr_alarms); \
|
||||
} while (0)
|
||||
|
||||
#define device_create_file_control(client) \
|
||||
do { \
|
||||
device_create_file(&client->dev, &dev_attr_control); \
|
||||
} while (0)
|
||||
|
||||
#define device_create_file_watchdog(client) \
|
||||
do { \
|
||||
device_create_file(&client->dev, &dev_attr_watchdog_status); \
|
||||
device_create_file(&client->dev, &dev_attr_watchdog_control); \
|
||||
device_create_file(&client->dev, &dev_attr_watchdog_preset); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static int fscher_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, fscher_detect);
|
||||
}
|
||||
|
||||
static int fscher_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct fscher_data *data;
|
||||
int err = 0;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
* client structure, even though we cannot fill it completely yet.
|
||||
* But it allows us to access i2c_smbus_read_byte_data. */
|
||||
if (!(data = kmalloc(sizeof(struct fscher_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct fscher_data));
|
||||
|
||||
/* The common I2C client data is placed right before the
|
||||
* Hermes-specific data. */
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &fscher_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Do the remaining detection unless force or force_fscher parameter */
|
||||
if (kind < 0) {
|
||||
if ((i2c_smbus_read_byte_data(new_client,
|
||||
FSCHER_REG_IDENT_0) != 0x48) /* 'H' */
|
||||
|| (i2c_smbus_read_byte_data(new_client,
|
||||
FSCHER_REG_IDENT_1) != 0x45) /* 'E' */
|
||||
|| (i2c_smbus_read_byte_data(new_client,
|
||||
FSCHER_REG_IDENT_2) != 0x52)) /* 'R' */
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields and put it into the
|
||||
* global list */
|
||||
strlcpy(new_client->name, "fscher", I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
fscher_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file_revision(new_client);
|
||||
device_create_file_alarms(new_client);
|
||||
device_create_file_control(new_client);
|
||||
device_create_file_watchdog(new_client);
|
||||
|
||||
device_create_file_in(new_client, 0);
|
||||
device_create_file_in(new_client, 1);
|
||||
device_create_file_in(new_client, 2);
|
||||
|
||||
device_create_file_fan(new_client, 1);
|
||||
device_create_file_fan(new_client, 2);
|
||||
device_create_file_fan(new_client, 3);
|
||||
|
||||
device_create_file_temp(new_client, 1);
|
||||
device_create_file_temp(new_client, 2);
|
||||
device_create_file_temp(new_client, 3);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int fscher_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, "
|
||||
"client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fscher_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
dev_dbg(&client->dev, "read reg 0x%02x\n", reg);
|
||||
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
static int fscher_write_value(struct i2c_client *client, u8 reg, u8 value)
|
||||
{
|
||||
dev_dbg(&client->dev, "write reg 0x%02x, val 0x%02x\n",
|
||||
reg, value);
|
||||
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
/* Called when we have found a new FSC Hermes. */
|
||||
static void fscher_init_client(struct i2c_client *client)
|
||||
{
|
||||
struct fscher_data *data = i2c_get_clientdata(client);
|
||||
|
||||
/* Read revision from chip */
|
||||
data->revision = fscher_read_value(client, FSCHER_REG_REVISION);
|
||||
}
|
||||
|
||||
static struct fscher_data *fscher_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct fscher_data *data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) {
|
||||
|
||||
dev_dbg(&client->dev, "Starting fscher update\n");
|
||||
|
||||
data->temp_act[0] = fscher_read_value(client, FSCHER_REG_TEMP0_ACT);
|
||||
data->temp_act[1] = fscher_read_value(client, FSCHER_REG_TEMP1_ACT);
|
||||
data->temp_act[2] = fscher_read_value(client, FSCHER_REG_TEMP2_ACT);
|
||||
data->temp_status[0] = fscher_read_value(client, FSCHER_REG_TEMP0_STATE);
|
||||
data->temp_status[1] = fscher_read_value(client, FSCHER_REG_TEMP1_STATE);
|
||||
data->temp_status[2] = fscher_read_value(client, FSCHER_REG_TEMP2_STATE);
|
||||
|
||||
data->volt[0] = fscher_read_value(client, FSCHER_REG_VOLT_12);
|
||||
data->volt[1] = fscher_read_value(client, FSCHER_REG_VOLT_5);
|
||||
data->volt[2] = fscher_read_value(client, FSCHER_REG_VOLT_BATT);
|
||||
|
||||
data->fan_act[0] = fscher_read_value(client, FSCHER_REG_FAN0_ACT);
|
||||
data->fan_act[1] = fscher_read_value(client, FSCHER_REG_FAN1_ACT);
|
||||
data->fan_act[2] = fscher_read_value(client, FSCHER_REG_FAN2_ACT);
|
||||
data->fan_status[0] = fscher_read_value(client, FSCHER_REG_FAN0_STATE);
|
||||
data->fan_status[1] = fscher_read_value(client, FSCHER_REG_FAN1_STATE);
|
||||
data->fan_status[2] = fscher_read_value(client, FSCHER_REG_FAN2_STATE);
|
||||
data->fan_min[0] = fscher_read_value(client, FSCHER_REG_FAN0_MIN);
|
||||
data->fan_min[1] = fscher_read_value(client, FSCHER_REG_FAN1_MIN);
|
||||
data->fan_min[2] = fscher_read_value(client, FSCHER_REG_FAN2_MIN);
|
||||
data->fan_ripple[0] = fscher_read_value(client, FSCHER_REG_FAN0_RIPPLE);
|
||||
data->fan_ripple[1] = fscher_read_value(client, FSCHER_REG_FAN1_RIPPLE);
|
||||
data->fan_ripple[2] = fscher_read_value(client, FSCHER_REG_FAN2_RIPPLE);
|
||||
|
||||
data->watchdog[0] = fscher_read_value(client, FSCHER_REG_WDOG_PRESET);
|
||||
data->watchdog[1] = fscher_read_value(client, FSCHER_REG_WDOG_STATE);
|
||||
data->watchdog[2] = fscher_read_value(client, FSCHER_REG_WDOG_CONTROL);
|
||||
|
||||
data->global_event = fscher_read_value(client, FSCHER_REG_EVENT_STATE);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define FAN_INDEX_FROM_NUM(nr) ((nr) - 1)
|
||||
|
||||
static ssize_t set_fan_status(struct i2c_client *client, struct fscher_data *data,
|
||||
const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
/* bits 0..1, 3..7 reserved => mask with 0x04 */
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0x04;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->fan_status[FAN_INDEX_FROM_NUM(nr)] &= ~v;
|
||||
fscher_write_value(client, reg, v);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_fan_status(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 0..1, 3..7 reserved => mask with 0x04 */
|
||||
return sprintf(buf, "%u\n", data->fan_status[FAN_INDEX_FROM_NUM(nr)] & 0x04);
|
||||
}
|
||||
|
||||
static ssize_t set_pwm(struct i2c_client *client, struct fscher_data *data,
|
||||
const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->fan_min[FAN_INDEX_FROM_NUM(nr)] = v > 0xff ? 0xff : v;
|
||||
fscher_write_value(client, reg, data->fan_min[FAN_INDEX_FROM_NUM(nr)]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_pwm(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%u\n", data->fan_min[FAN_INDEX_FROM_NUM(nr)]);
|
||||
}
|
||||
|
||||
static ssize_t set_fan_div(struct i2c_client *client, struct fscher_data *data,
|
||||
const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
/* supported values: 2, 4, 8 */
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
switch (v) {
|
||||
case 2: v = 1; break;
|
||||
case 4: v = 2; break;
|
||||
case 8: v = 3; break;
|
||||
default:
|
||||
dev_err(&client->dev, "fan_div value %ld not "
|
||||
"supported. Choose one of 2, 4 or 8!\n", v);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
/* bits 2..7 reserved => mask with 0x03 */
|
||||
data->fan_ripple[FAN_INDEX_FROM_NUM(nr)] &= ~0x03;
|
||||
data->fan_ripple[FAN_INDEX_FROM_NUM(nr)] |= v;
|
||||
|
||||
fscher_write_value(client, reg, data->fan_ripple[FAN_INDEX_FROM_NUM(nr)]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_fan_div(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 2..7 reserved => mask with 0x03 */
|
||||
return sprintf(buf, "%u\n", 1 << (data->fan_ripple[FAN_INDEX_FROM_NUM(nr)] & 0x03));
|
||||
}
|
||||
|
||||
#define RPM_FROM_REG(val) (val*60)
|
||||
|
||||
static ssize_t show_fan_input (struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%u\n", RPM_FROM_REG(data->fan_act[FAN_INDEX_FROM_NUM(nr)]));
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define TEMP_INDEX_FROM_NUM(nr) ((nr) - 1)
|
||||
|
||||
static ssize_t set_temp_status(struct i2c_client *client, struct fscher_data *data,
|
||||
const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
/* bits 2..7 reserved, 0 read only => mask with 0x02 */
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0x02;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp_status[TEMP_INDEX_FROM_NUM(nr)] &= ~v;
|
||||
fscher_write_value(client, reg, v);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_temp_status(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 2..7 reserved => mask with 0x03 */
|
||||
return sprintf(buf, "%u\n", data->temp_status[TEMP_INDEX_FROM_NUM(nr)] & 0x03);
|
||||
}
|
||||
|
||||
#define TEMP_FROM_REG(val) (((val) - 128) * 1000)
|
||||
|
||||
static ssize_t show_temp_input(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_act[TEMP_INDEX_FROM_NUM(nr)]));
|
||||
}
|
||||
|
||||
/*
|
||||
* The final conversion is specified in sensors.conf, as it depends on
|
||||
* mainboard specific values. We export the registers contents as
|
||||
* pseudo-hundredths-of-Volts (range 0V - 2.55V). Not that it makes much
|
||||
* sense per se, but it minimizes the conversions count and keeps the
|
||||
* values within a usual range.
|
||||
*/
|
||||
#define VOLT_FROM_REG(val) ((val) * 10)
|
||||
|
||||
static ssize_t show_in_input(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%u\n", VOLT_FROM_REG(data->volt[nr]));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ssize_t show_revision(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%u\n", data->revision);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ssize_t show_alarms(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 2, 5..6 reserved => mask with 0x9b */
|
||||
return sprintf(buf, "%u\n", data->global_event & 0x9b);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ssize_t set_control(struct i2c_client *client, struct fscher_data *data,
|
||||
const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
/* bits 1..7 reserved => mask with 0x01 */
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0x01;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->global_control &= ~v;
|
||||
fscher_write_value(client, reg, v);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_control(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 1..7 reserved => mask with 0x01 */
|
||||
return sprintf(buf, "%u\n", data->global_control & 0x01);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ssize_t set_watchdog_control(struct i2c_client *client, struct
|
||||
fscher_data *data, const char *buf, size_t count,
|
||||
int nr, int reg)
|
||||
{
|
||||
/* bits 0..3 reserved => mask with 0xf0 */
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0xf0;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->watchdog[2] &= ~0xf0;
|
||||
data->watchdog[2] |= v;
|
||||
fscher_write_value(client, reg, data->watchdog[2]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_watchdog_control(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 0..3 reserved, bit 5 write only => mask with 0xd0 */
|
||||
return sprintf(buf, "%u\n", data->watchdog[2] & 0xd0);
|
||||
}
|
||||
|
||||
static ssize_t set_watchdog_status(struct i2c_client *client, struct fscher_data *data,
|
||||
const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
/* bits 0, 2..7 reserved => mask with 0x02 */
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0x02;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->watchdog[1] &= ~v;
|
||||
fscher_write_value(client, reg, v);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_watchdog_status(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 0, 2..7 reserved => mask with 0x02 */
|
||||
return sprintf(buf, "%u\n", data->watchdog[1] & 0x02);
|
||||
}
|
||||
|
||||
static ssize_t set_watchdog_preset(struct i2c_client *client, struct fscher_data *data,
|
||||
const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0xff;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->watchdog[0] = v;
|
||||
fscher_write_value(client, reg, data->watchdog[0]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_watchdog_preset(struct fscher_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%u\n", data->watchdog[0]);
|
||||
}
|
||||
|
||||
static int __init sensors_fscher_init(void)
|
||||
{
|
||||
return i2c_add_driver(&fscher_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_fscher_exit(void)
|
||||
{
|
||||
i2c_del_driver(&fscher_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Reinhard Nissl <rnissl@gmx.de>");
|
||||
MODULE_DESCRIPTION("FSC Hermes driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_fscher_init);
|
||||
module_exit(sensors_fscher_exit);
|
||||
641
drivers/hwmon/fscpos.c
Normal file
641
drivers/hwmon/fscpos.c
Normal file
|
|
@ -0,0 +1,641 @@
|
|||
/*
|
||||
fscpos.c - Kernel module for hardware monitoring with FSC Poseidon chips
|
||||
Copyright (C) 2004, 2005 Stefan Ott <stefan@desire.ch>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
fujitsu siemens poseidon chip,
|
||||
module based on the old fscpos module by Hermann Jung <hej@odn.de> and
|
||||
the fscher module by Reinhard Nissl <rnissl@gmx.de>
|
||||
|
||||
original module based on lm80.c
|
||||
Copyright (C) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
|
||||
and Philip Edelbrock <phil@netroedge.com>
|
||||
|
||||
Thanks to Jean Delvare for reviewing my code and suggesting a lot of
|
||||
improvements.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-sensor.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
/*
|
||||
* Addresses to scan
|
||||
*/
|
||||
static unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/*
|
||||
* Insmod parameters
|
||||
*/
|
||||
SENSORS_INSMOD_1(fscpos);
|
||||
|
||||
/*
|
||||
* The FSCPOS registers
|
||||
*/
|
||||
|
||||
/* chip identification */
|
||||
#define FSCPOS_REG_IDENT_0 0x00
|
||||
#define FSCPOS_REG_IDENT_1 0x01
|
||||
#define FSCPOS_REG_IDENT_2 0x02
|
||||
#define FSCPOS_REG_REVISION 0x03
|
||||
|
||||
/* global control and status */
|
||||
#define FSCPOS_REG_EVENT_STATE 0x04
|
||||
#define FSCPOS_REG_CONTROL 0x05
|
||||
|
||||
/* watchdog */
|
||||
#define FSCPOS_REG_WDOG_PRESET 0x28
|
||||
#define FSCPOS_REG_WDOG_STATE 0x23
|
||||
#define FSCPOS_REG_WDOG_CONTROL 0x21
|
||||
|
||||
/* voltages */
|
||||
#define FSCPOS_REG_VOLT_12 0x45
|
||||
#define FSCPOS_REG_VOLT_5 0x42
|
||||
#define FSCPOS_REG_VOLT_BATT 0x48
|
||||
|
||||
/* fans - the chip does not support minimum speed for fan2 */
|
||||
static u8 FSCPOS_REG_PWM[] = { 0x55, 0x65 };
|
||||
static u8 FSCPOS_REG_FAN_ACT[] = { 0x0e, 0x6b, 0xab };
|
||||
static u8 FSCPOS_REG_FAN_STATE[] = { 0x0d, 0x62, 0xa2 };
|
||||
static u8 FSCPOS_REG_FAN_RIPPLE[] = { 0x0f, 0x6f, 0xaf };
|
||||
|
||||
/* temperatures */
|
||||
static u8 FSCPOS_REG_TEMP_ACT[] = { 0x64, 0x32, 0x35 };
|
||||
static u8 FSCPOS_REG_TEMP_STATE[] = { 0x71, 0x81, 0x91 };
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
*/
|
||||
static int fscpos_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int fscpos_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static int fscpos_detach_client(struct i2c_client *client);
|
||||
|
||||
static int fscpos_read_value(struct i2c_client *client, u8 register);
|
||||
static int fscpos_write_value(struct i2c_client *client, u8 register, u8 value);
|
||||
static struct fscpos_data *fscpos_update_device(struct device *dev);
|
||||
static void fscpos_init_client(struct i2c_client *client);
|
||||
|
||||
static void reset_fan_alarm(struct i2c_client *client, int nr);
|
||||
|
||||
/*
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
static struct i2c_driver fscpos_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "fscpos",
|
||||
.id = I2C_DRIVERID_FSCPOS,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = fscpos_attach_adapter,
|
||||
.detach_client = fscpos_detach_client,
|
||||
};
|
||||
|
||||
/*
|
||||
* Client data (each client gets its own)
|
||||
*/
|
||||
struct fscpos_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid; /* 0 until following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
||||
/* register values */
|
||||
u8 revision; /* revision of chip */
|
||||
u8 global_event; /* global event status */
|
||||
u8 global_control; /* global control register */
|
||||
u8 wdog_control; /* watchdog control */
|
||||
u8 wdog_state; /* watchdog status */
|
||||
u8 wdog_preset; /* watchdog preset */
|
||||
u8 volt[3]; /* 12, 5, battery current */
|
||||
u8 temp_act[3]; /* temperature */
|
||||
u8 temp_status[3]; /* status of sensor */
|
||||
u8 fan_act[3]; /* fans revolutions per second */
|
||||
u8 fan_status[3]; /* fan status */
|
||||
u8 pwm[2]; /* fan min value for rps */
|
||||
u8 fan_ripple[3]; /* divider for rps */
|
||||
};
|
||||
|
||||
/* Temperature */
|
||||
#define TEMP_FROM_REG(val) (((val) - 128) * 1000)
|
||||
|
||||
static ssize_t show_temp_input(struct fscpos_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_act[nr - 1]));
|
||||
}
|
||||
|
||||
static ssize_t show_temp_status(struct fscpos_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 2..7 reserved => mask with 0x03 */
|
||||
return sprintf(buf, "%u\n", data->temp_status[nr - 1] & 0x03);
|
||||
}
|
||||
|
||||
static ssize_t show_temp_reset(struct fscpos_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "1\n");
|
||||
}
|
||||
|
||||
static ssize_t set_temp_reset(struct i2c_client *client, struct fscpos_data
|
||||
*data, const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10);
|
||||
if (v != 1) {
|
||||
dev_err(&client->dev, "temp_reset value %ld not supported. "
|
||||
"Use 1 to reset the alarm!\n", v);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "You used the temp_reset feature which has not "
|
||||
"been proplerly tested. Please report your "
|
||||
"experience to the module author.\n");
|
||||
|
||||
/* Supported value: 2 (clears the status) */
|
||||
fscpos_write_value(client, FSCPOS_REG_TEMP_STATE[nr], 2);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Fans */
|
||||
#define RPM_FROM_REG(val) ((val) * 60)
|
||||
|
||||
static ssize_t show_fan_status(struct fscpos_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 0..1, 3..7 reserved => mask with 0x04 */
|
||||
return sprintf(buf, "%u\n", data->fan_status[nr - 1] & 0x04);
|
||||
}
|
||||
|
||||
static ssize_t show_fan_input(struct fscpos_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%u\n", RPM_FROM_REG(data->fan_act[nr - 1]));
|
||||
}
|
||||
|
||||
static ssize_t show_fan_ripple(struct fscpos_data *data, char *buf, int nr)
|
||||
{
|
||||
/* bits 2..7 reserved => mask with 0x03 */
|
||||
return sprintf(buf, "%u\n", data->fan_ripple[nr - 1] & 0x03);
|
||||
}
|
||||
|
||||
static ssize_t set_fan_ripple(struct i2c_client *client, struct fscpos_data
|
||||
*data, const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
/* supported values: 2, 4, 8 */
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
switch (v) {
|
||||
case 2: v = 1; break;
|
||||
case 4: v = 2; break;
|
||||
case 8: v = 3; break;
|
||||
default:
|
||||
dev_err(&client->dev, "fan_ripple value %ld not supported. "
|
||||
"Must be one of 2, 4 or 8!\n", v);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
down(&data->update_lock);
|
||||
/* bits 2..7 reserved => mask with 0x03 */
|
||||
data->fan_ripple[nr - 1] &= ~0x03;
|
||||
data->fan_ripple[nr - 1] |= v;
|
||||
|
||||
fscpos_write_value(client, reg, data->fan_ripple[nr - 1]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_pwm(struct fscpos_data *data, char *buf, int nr)
|
||||
{
|
||||
return sprintf(buf, "%u\n", data->pwm[nr - 1]);
|
||||
}
|
||||
|
||||
static ssize_t set_pwm(struct i2c_client *client, struct fscpos_data *data,
|
||||
const char *buf, size_t count, int nr, int reg)
|
||||
{
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
/* Range: 0..255 */
|
||||
if (v < 0) v = 0;
|
||||
if (v > 255) v = 255;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->pwm[nr - 1] = v;
|
||||
fscpos_write_value(client, reg, data->pwm[nr - 1]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static void reset_fan_alarm(struct i2c_client *client, int nr)
|
||||
{
|
||||
fscpos_write_value(client, FSCPOS_REG_FAN_STATE[nr], 4);
|
||||
}
|
||||
|
||||
/* Volts */
|
||||
#define VOLT_FROM_REG(val, mult) ((val) * (mult) / 255)
|
||||
|
||||
static ssize_t show_volt_12(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fscpos_data *data = fscpos_update_device(dev);
|
||||
return sprintf(buf, "%u\n", VOLT_FROM_REG(data->volt[0], 14200));
|
||||
}
|
||||
|
||||
static ssize_t show_volt_5(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fscpos_data *data = fscpos_update_device(dev);
|
||||
return sprintf(buf, "%u\n", VOLT_FROM_REG(data->volt[1], 6600));
|
||||
}
|
||||
|
||||
static ssize_t show_volt_batt(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fscpos_data *data = fscpos_update_device(dev);
|
||||
return sprintf(buf, "%u\n", VOLT_FROM_REG(data->volt[2], 3300));
|
||||
}
|
||||
|
||||
/* Watchdog */
|
||||
static ssize_t show_wdog_control(struct fscpos_data *data, char *buf)
|
||||
{
|
||||
/* bits 0..3 reserved, bit 6 write only => mask with 0xb0 */
|
||||
return sprintf(buf, "%u\n", data->wdog_control & 0xb0);
|
||||
}
|
||||
|
||||
static ssize_t set_wdog_control(struct i2c_client *client, struct fscpos_data
|
||||
*data, const char *buf, size_t count, int reg)
|
||||
{
|
||||
/* bits 0..3 reserved => mask with 0xf0 */
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0xf0;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->wdog_control &= ~0xf0;
|
||||
data->wdog_control |= v;
|
||||
fscpos_write_value(client, reg, data->wdog_control);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_wdog_state(struct fscpos_data *data, char *buf)
|
||||
{
|
||||
/* bits 0, 2..7 reserved => mask with 0x02 */
|
||||
return sprintf(buf, "%u\n", data->wdog_state & 0x02);
|
||||
}
|
||||
|
||||
static ssize_t set_wdog_state(struct i2c_client *client, struct fscpos_data
|
||||
*data, const char *buf, size_t count, int reg)
|
||||
{
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0x02;
|
||||
|
||||
/* Valid values: 2 (clear) */
|
||||
if (v != 2) {
|
||||
dev_err(&client->dev, "wdog_state value %ld not supported. "
|
||||
"Must be 2 to clear the state!\n", v);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
down(&data->update_lock);
|
||||
data->wdog_state &= ~v;
|
||||
fscpos_write_value(client, reg, v);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_wdog_preset(struct fscpos_data *data, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", data->wdog_preset);
|
||||
}
|
||||
|
||||
static ssize_t set_wdog_preset(struct i2c_client *client, struct fscpos_data
|
||||
*data, const char *buf, size_t count, int reg)
|
||||
{
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10) & 0xff;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->wdog_preset = v;
|
||||
fscpos_write_value(client, reg, data->wdog_preset);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Event */
|
||||
static ssize_t show_event(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
/* bits 5..7 reserved => mask with 0x1f */
|
||||
struct fscpos_data *data = fscpos_update_device(dev);
|
||||
return sprintf(buf, "%u\n", data->global_event & 0x9b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
#define create_getter(kind, sub) \
|
||||
static ssize_t sysfs_show_##kind##sub(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct fscpos_data *data = fscpos_update_device(dev); \
|
||||
return show_##kind##sub(data, buf); \
|
||||
}
|
||||
|
||||
#define create_getter_n(kind, offset, sub) \
|
||||
static ssize_t sysfs_show_##kind##offset##sub(struct device *dev, struct device_attribute *attr, char\
|
||||
*buf) \
|
||||
{ \
|
||||
struct fscpos_data *data = fscpos_update_device(dev); \
|
||||
return show_##kind##sub(data, buf, offset); \
|
||||
}
|
||||
|
||||
#define create_setter(kind, sub, reg) \
|
||||
static ssize_t sysfs_set_##kind##sub (struct device *dev, struct device_attribute *attr, const char \
|
||||
*buf, size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct fscpos_data *data = i2c_get_clientdata(client); \
|
||||
return set_##kind##sub(client, data, buf, count, reg); \
|
||||
}
|
||||
|
||||
#define create_setter_n(kind, offset, sub, reg) \
|
||||
static ssize_t sysfs_set_##kind##offset##sub (struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct fscpos_data *data = i2c_get_clientdata(client); \
|
||||
return set_##kind##sub(client, data, buf, count, offset, reg);\
|
||||
}
|
||||
|
||||
#define create_sysfs_device_ro(kind, sub, offset) \
|
||||
static DEVICE_ATTR(kind##offset##sub, S_IRUGO, \
|
||||
sysfs_show_##kind##offset##sub, NULL);
|
||||
|
||||
#define create_sysfs_device_rw(kind, sub, offset) \
|
||||
static DEVICE_ATTR(kind##offset##sub, S_IRUGO | S_IWUSR, \
|
||||
sysfs_show_##kind##offset##sub, sysfs_set_##kind##offset##sub);
|
||||
|
||||
#define sysfs_ro_n(kind, sub, offset) \
|
||||
create_getter_n(kind, offset, sub); \
|
||||
create_sysfs_device_ro(kind, sub, offset);
|
||||
|
||||
#define sysfs_rw_n(kind, sub, offset, reg) \
|
||||
create_getter_n(kind, offset, sub); \
|
||||
create_setter_n(kind, offset, sub, reg); \
|
||||
create_sysfs_device_rw(kind, sub, offset);
|
||||
|
||||
#define sysfs_rw(kind, sub, reg) \
|
||||
create_getter(kind, sub); \
|
||||
create_setter(kind, sub, reg); \
|
||||
create_sysfs_device_rw(kind, sub,);
|
||||
|
||||
#define sysfs_fan_with_min(offset, reg_status, reg_ripple, reg_min) \
|
||||
sysfs_fan(offset, reg_status, reg_ripple); \
|
||||
sysfs_rw_n(pwm,, offset, reg_min);
|
||||
|
||||
#define sysfs_fan(offset, reg_status, reg_ripple) \
|
||||
sysfs_ro_n(fan, _input, offset); \
|
||||
sysfs_ro_n(fan, _status, offset); \
|
||||
sysfs_rw_n(fan, _ripple, offset, reg_ripple);
|
||||
|
||||
#define sysfs_temp(offset, reg_status) \
|
||||
sysfs_ro_n(temp, _input, offset); \
|
||||
sysfs_ro_n(temp, _status, offset); \
|
||||
sysfs_rw_n(temp, _reset, offset, reg_status);
|
||||
|
||||
#define sysfs_watchdog(reg_wdog_preset, reg_wdog_state, reg_wdog_control) \
|
||||
sysfs_rw(wdog, _control, reg_wdog_control); \
|
||||
sysfs_rw(wdog, _preset, reg_wdog_preset); \
|
||||
sysfs_rw(wdog, _state, reg_wdog_state);
|
||||
|
||||
sysfs_fan_with_min(1, FSCPOS_REG_FAN_STATE[0], FSCPOS_REG_FAN_RIPPLE[0],
|
||||
FSCPOS_REG_PWM[0]);
|
||||
sysfs_fan_with_min(2, FSCPOS_REG_FAN_STATE[1], FSCPOS_REG_FAN_RIPPLE[1],
|
||||
FSCPOS_REG_PWM[1]);
|
||||
sysfs_fan(3, FSCPOS_REG_FAN_STATE[2], FSCPOS_REG_FAN_RIPPLE[2]);
|
||||
|
||||
sysfs_temp(1, FSCPOS_REG_TEMP_STATE[0]);
|
||||
sysfs_temp(2, FSCPOS_REG_TEMP_STATE[1]);
|
||||
sysfs_temp(3, FSCPOS_REG_TEMP_STATE[2]);
|
||||
|
||||
sysfs_watchdog(FSCPOS_REG_WDOG_PRESET, FSCPOS_REG_WDOG_STATE,
|
||||
FSCPOS_REG_WDOG_CONTROL);
|
||||
|
||||
static DEVICE_ATTR(event, S_IRUGO, show_event, NULL);
|
||||
static DEVICE_ATTR(in0_input, S_IRUGO, show_volt_12, NULL);
|
||||
static DEVICE_ATTR(in1_input, S_IRUGO, show_volt_5, NULL);
|
||||
static DEVICE_ATTR(in2_input, S_IRUGO, show_volt_batt, NULL);
|
||||
|
||||
static int fscpos_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, fscpos_detect);
|
||||
}
|
||||
|
||||
int fscpos_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct fscpos_data *data;
|
||||
int err = 0;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
/*
|
||||
* OK. For now, we presume we have a valid client. We now create the
|
||||
* client structure, even though we cannot fill it completely yet.
|
||||
* But it allows us to access fscpos_{read,write}_value.
|
||||
*/
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct fscpos_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct fscpos_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &fscpos_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Do the remaining detection unless force or force_fscpos parameter */
|
||||
if (kind < 0) {
|
||||
if ((fscpos_read_value(new_client, FSCPOS_REG_IDENT_0)
|
||||
!= 0x50) /* 'P' */
|
||||
|| (fscpos_read_value(new_client, FSCPOS_REG_IDENT_1)
|
||||
!= 0x45) /* 'E' */
|
||||
|| (fscpos_read_value(new_client, FSCPOS_REG_IDENT_2)
|
||||
!= 0x47))/* 'G' */
|
||||
{
|
||||
dev_dbg(&new_client->dev, "fscpos detection failed\n");
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields and put it in the global list */
|
||||
strlcpy(new_client->name, "fscpos", I2C_NAME_SIZE);
|
||||
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Inizialize the fscpos chip */
|
||||
fscpos_init_client(new_client);
|
||||
|
||||
/* Announce that the chip was found */
|
||||
dev_info(&new_client->dev, "Found fscpos chip, rev %u\n", data->revision);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_event);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_wdog_control);
|
||||
device_create_file(&new_client->dev, &dev_attr_wdog_preset);
|
||||
device_create_file(&new_client->dev, &dev_attr_wdog_state);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_status);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_reset);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_status);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_reset);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp3_status);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp3_reset);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_status);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_ripple);
|
||||
device_create_file(&new_client->dev, &dev_attr_pwm1);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_status);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_ripple);
|
||||
device_create_file(&new_client->dev, &dev_attr_pwm2);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan3_status);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan3_ripple);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int fscpos_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, client"
|
||||
" not detached.\n");
|
||||
return err;
|
||||
}
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fscpos_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
dev_dbg(&client->dev, "Read reg 0x%02x\n", reg);
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
static int fscpos_write_value(struct i2c_client *client, u8 reg, u8 value)
|
||||
{
|
||||
dev_dbg(&client->dev, "Write reg 0x%02x, val 0x%02x\n", reg, value);
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
/* Called when we have found a new FSCPOS chip */
|
||||
static void fscpos_init_client(struct i2c_client *client)
|
||||
{
|
||||
struct fscpos_data *data = i2c_get_clientdata(client);
|
||||
|
||||
/* read revision from chip */
|
||||
data->revision = fscpos_read_value(client, FSCPOS_REG_REVISION);
|
||||
}
|
||||
|
||||
static struct fscpos_data *fscpos_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct fscpos_data *data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if ((jiffies - data->last_updated > 2 * HZ) ||
|
||||
(jiffies < data->last_updated) || !data->valid) {
|
||||
int i;
|
||||
|
||||
dev_dbg(&client->dev, "Starting fscpos update\n");
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
data->temp_act[i] = fscpos_read_value(client,
|
||||
FSCPOS_REG_TEMP_ACT[i]);
|
||||
data->temp_status[i] = fscpos_read_value(client,
|
||||
FSCPOS_REG_TEMP_STATE[i]);
|
||||
data->fan_act[i] = fscpos_read_value(client,
|
||||
FSCPOS_REG_FAN_ACT[i]);
|
||||
data->fan_status[i] = fscpos_read_value(client,
|
||||
FSCPOS_REG_FAN_STATE[i]);
|
||||
data->fan_ripple[i] = fscpos_read_value(client,
|
||||
FSCPOS_REG_FAN_RIPPLE[i]);
|
||||
if (i < 2) {
|
||||
/* fan2_min is not supported by the chip */
|
||||
data->pwm[i] = fscpos_read_value(client,
|
||||
FSCPOS_REG_PWM[i]);
|
||||
}
|
||||
/* reset fan status if speed is back to > 0 */
|
||||
if (data->fan_status[i] != 0 && data->fan_act[i] > 0) {
|
||||
reset_fan_alarm(client, i);
|
||||
}
|
||||
}
|
||||
|
||||
data->volt[0] = fscpos_read_value(client, FSCPOS_REG_VOLT_12);
|
||||
data->volt[1] = fscpos_read_value(client, FSCPOS_REG_VOLT_5);
|
||||
data->volt[2] = fscpos_read_value(client, FSCPOS_REG_VOLT_BATT);
|
||||
|
||||
data->wdog_preset = fscpos_read_value(client,
|
||||
FSCPOS_REG_WDOG_PRESET);
|
||||
data->wdog_state = fscpos_read_value(client,
|
||||
FSCPOS_REG_WDOG_STATE);
|
||||
data->wdog_control = fscpos_read_value(client,
|
||||
FSCPOS_REG_WDOG_CONTROL);
|
||||
|
||||
data->global_event = fscpos_read_value(client,
|
||||
FSCPOS_REG_EVENT_STATE);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
up(&data->update_lock);
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sm_fscpos_init(void)
|
||||
{
|
||||
return i2c_add_driver(&fscpos_driver);
|
||||
}
|
||||
|
||||
static void __exit sm_fscpos_exit(void)
|
||||
{
|
||||
i2c_del_driver(&fscpos_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Stefan Ott <stefan@desire.ch> based on work from Hermann Jung "
|
||||
"<hej@odn.de>, Frodo Looijaard <frodol@dds.nl>"
|
||||
" and Philip Edelbrock <phil@netroedge.com>");
|
||||
MODULE_DESCRIPTION("fujitsu siemens poseidon chip driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sm_fscpos_init);
|
||||
module_exit(sm_fscpos_exit);
|
||||
604
drivers/hwmon/gl518sm.c
Normal file
604
drivers/hwmon/gl518sm.c
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
/*
|
||||
* gl518sm.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
* monitoring
|
||||
* Copyright (C) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and
|
||||
* Kyosti Malkki <kmalkki@cc.hut.fi>
|
||||
* Copyright (C) 2004 Hong-Gunn Chew <hglinux@gunnet.org> and
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* Ported to Linux 2.6 by Hong-Gunn Chew with the help of Jean Delvare
|
||||
* and advice of Greg Kroah-Hartman.
|
||||
*
|
||||
* Notes about the port:
|
||||
* Release 0x00 of the GL518SM chipset doesn't support reading of in0,
|
||||
* in1 nor in2. The original driver had an ugly workaround to get them
|
||||
* anyway (changing limits and watching alarms trigger and wear off).
|
||||
* We did not keep that part of the original driver in the Linux 2.6
|
||||
* version, since it was making the driver significantly more complex
|
||||
* with no real benefit.
|
||||
*
|
||||
* History:
|
||||
* 2004-01-28 Original port. (Hong-Gunn Chew)
|
||||
* 2004-01-31 Code review and approval. (Jean Delvare)
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-sensor.h>
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_2(gl518sm_r00, gl518sm_r80);
|
||||
|
||||
/* Many GL518 constants specified below */
|
||||
|
||||
/* The GL518 registers */
|
||||
#define GL518_REG_CHIP_ID 0x00
|
||||
#define GL518_REG_REVISION 0x01
|
||||
#define GL518_REG_VENDOR_ID 0x02
|
||||
#define GL518_REG_CONF 0x03
|
||||
#define GL518_REG_TEMP_IN 0x04
|
||||
#define GL518_REG_TEMP_MAX 0x05
|
||||
#define GL518_REG_TEMP_HYST 0x06
|
||||
#define GL518_REG_FAN_COUNT 0x07
|
||||
#define GL518_REG_FAN_LIMIT 0x08
|
||||
#define GL518_REG_VIN1_LIMIT 0x09
|
||||
#define GL518_REG_VIN2_LIMIT 0x0a
|
||||
#define GL518_REG_VIN3_LIMIT 0x0b
|
||||
#define GL518_REG_VDD_LIMIT 0x0c
|
||||
#define GL518_REG_VIN3 0x0d
|
||||
#define GL518_REG_MISC 0x0f
|
||||
#define GL518_REG_ALARM 0x10
|
||||
#define GL518_REG_MASK 0x11
|
||||
#define GL518_REG_INT 0x12
|
||||
#define GL518_REG_VIN2 0x13
|
||||
#define GL518_REG_VIN1 0x14
|
||||
#define GL518_REG_VDD 0x15
|
||||
|
||||
|
||||
/*
|
||||
* Conversions. Rounding and limit checking is only done on the TO_REG
|
||||
* variants. Note that you should be a bit careful with which arguments
|
||||
* these macros are called: arguments may be evaluated more than once.
|
||||
* Fixing this is just not worth it.
|
||||
*/
|
||||
|
||||
#define RAW_FROM_REG(val) val
|
||||
|
||||
#define BOOL_FROM_REG(val) ((val)?0:1)
|
||||
#define BOOL_TO_REG(val) ((val)?0:1)
|
||||
|
||||
#define TEMP_TO_REG(val) (SENSORS_LIMIT(((((val)<0? \
|
||||
(val)-500:(val)+500)/1000)+119),0,255))
|
||||
#define TEMP_FROM_REG(val) (((val) - 119) * 1000)
|
||||
|
||||
static inline u8 FAN_TO_REG(long rpm, int div)
|
||||
{
|
||||
long rpmdiv;
|
||||
if (rpm == 0)
|
||||
return 0;
|
||||
rpmdiv = SENSORS_LIMIT(rpm, 1, 1920000) * div;
|
||||
return SENSORS_LIMIT((960000 + rpmdiv / 2) / rpmdiv, 1, 255);
|
||||
}
|
||||
#define FAN_FROM_REG(val,div) ((val)==0 ? 0 : (960000/((val)*(div))))
|
||||
|
||||
#define IN_TO_REG(val) (SENSORS_LIMIT((((val)+9)/19),0,255))
|
||||
#define IN_FROM_REG(val) ((val)*19)
|
||||
|
||||
#define VDD_TO_REG(val) (SENSORS_LIMIT((((val)*4+47)/95),0,255))
|
||||
#define VDD_FROM_REG(val) (((val)*95+2)/4)
|
||||
|
||||
#define DIV_TO_REG(val) ((val)==4?2:(val)==2?1:(val)==1?0:3)
|
||||
#define DIV_FROM_REG(val) (1 << (val))
|
||||
|
||||
#define BEEP_MASK_TO_REG(val) ((val) & 0x7f & data->alarm_mask)
|
||||
#define BEEP_MASK_FROM_REG(val) ((val) & 0x7f)
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct gl518_data {
|
||||
struct i2c_client client;
|
||||
enum chips type;
|
||||
|
||||
struct semaphore update_lock;
|
||||
char valid; /* !=0 if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
||||
u8 voltage_in[4]; /* Register values; [0] = VDD */
|
||||
u8 voltage_min[4]; /* Register values; [0] = VDD */
|
||||
u8 voltage_max[4]; /* Register values; [0] = VDD */
|
||||
u8 iter_voltage_in[4]; /* Register values; [0] = VDD */
|
||||
u8 fan_in[2];
|
||||
u8 fan_min[2];
|
||||
u8 fan_div[2]; /* Register encoding, shifted right */
|
||||
u8 fan_auto1; /* Boolean */
|
||||
u8 temp_in; /* Register values */
|
||||
u8 temp_max; /* Register values */
|
||||
u8 temp_hyst; /* Register values */
|
||||
u8 alarms; /* Register value */
|
||||
u8 alarm_mask; /* Register value */
|
||||
u8 beep_mask; /* Register value */
|
||||
u8 beep_enable; /* Boolean */
|
||||
};
|
||||
|
||||
static int gl518_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int gl518_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void gl518_init_client(struct i2c_client *client);
|
||||
static int gl518_detach_client(struct i2c_client *client);
|
||||
static int gl518_read_value(struct i2c_client *client, u8 reg);
|
||||
static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value);
|
||||
static struct gl518_data *gl518_update_device(struct device *dev);
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver gl518_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "gl518sm",
|
||||
.id = I2C_DRIVERID_GL518,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = gl518_attach_adapter,
|
||||
.detach_client = gl518_detach_client,
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
#define show(type, suffix, value) \
|
||||
static ssize_t show_##suffix(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct gl518_data *data = gl518_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", type##_FROM_REG(data->value)); \
|
||||
}
|
||||
|
||||
#define show_fan(suffix, value, index) \
|
||||
static ssize_t show_##suffix(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct gl518_data *data = gl518_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", FAN_FROM_REG(data->value[index], \
|
||||
DIV_FROM_REG(data->fan_div[index]))); \
|
||||
}
|
||||
|
||||
show(TEMP, temp_input1, temp_in);
|
||||
show(TEMP, temp_max1, temp_max);
|
||||
show(TEMP, temp_hyst1, temp_hyst);
|
||||
show(BOOL, fan_auto1, fan_auto1);
|
||||
show_fan(fan_input1, fan_in, 0);
|
||||
show_fan(fan_input2, fan_in, 1);
|
||||
show_fan(fan_min1, fan_min, 0);
|
||||
show_fan(fan_min2, fan_min, 1);
|
||||
show(DIV, fan_div1, fan_div[0]);
|
||||
show(DIV, fan_div2, fan_div[1]);
|
||||
show(VDD, in_input0, voltage_in[0]);
|
||||
show(IN, in_input1, voltage_in[1]);
|
||||
show(IN, in_input2, voltage_in[2]);
|
||||
show(IN, in_input3, voltage_in[3]);
|
||||
show(VDD, in_min0, voltage_min[0]);
|
||||
show(IN, in_min1, voltage_min[1]);
|
||||
show(IN, in_min2, voltage_min[2]);
|
||||
show(IN, in_min3, voltage_min[3]);
|
||||
show(VDD, in_max0, voltage_max[0]);
|
||||
show(IN, in_max1, voltage_max[1]);
|
||||
show(IN, in_max2, voltage_max[2]);
|
||||
show(IN, in_max3, voltage_max[3]);
|
||||
show(RAW, alarms, alarms);
|
||||
show(BOOL, beep_enable, beep_enable);
|
||||
show(BEEP_MASK, beep_mask, beep_mask);
|
||||
|
||||
#define set(type, suffix, value, reg) \
|
||||
static ssize_t set_##suffix(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct gl518_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtol(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->value = type##_TO_REG(val); \
|
||||
gl518_write_value(client, reg, data->value); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
}
|
||||
|
||||
#define set_bits(type, suffix, value, reg, mask, shift) \
|
||||
static ssize_t set_##suffix(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct gl518_data *data = i2c_get_clientdata(client); \
|
||||
int regvalue; \
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
regvalue = gl518_read_value(client, reg); \
|
||||
data->value = type##_TO_REG(val); \
|
||||
regvalue = (regvalue & ~mask) | (data->value << shift); \
|
||||
gl518_write_value(client, reg, regvalue); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
}
|
||||
|
||||
#define set_low(type, suffix, value, reg) \
|
||||
set_bits(type, suffix, value, reg, 0x00ff, 0)
|
||||
#define set_high(type, suffix, value, reg) \
|
||||
set_bits(type, suffix, value, reg, 0xff00, 8)
|
||||
|
||||
set(TEMP, temp_max1, temp_max, GL518_REG_TEMP_MAX);
|
||||
set(TEMP, temp_hyst1, temp_hyst, GL518_REG_TEMP_HYST);
|
||||
set_bits(BOOL, fan_auto1, fan_auto1, GL518_REG_MISC, 0x08, 3);
|
||||
set_bits(DIV, fan_div1, fan_div[0], GL518_REG_MISC, 0xc0, 6);
|
||||
set_bits(DIV, fan_div2, fan_div[1], GL518_REG_MISC, 0x30, 4);
|
||||
set_low(VDD, in_min0, voltage_min[0], GL518_REG_VDD_LIMIT);
|
||||
set_low(IN, in_min1, voltage_min[1], GL518_REG_VIN1_LIMIT);
|
||||
set_low(IN, in_min2, voltage_min[2], GL518_REG_VIN2_LIMIT);
|
||||
set_low(IN, in_min3, voltage_min[3], GL518_REG_VIN3_LIMIT);
|
||||
set_high(VDD, in_max0, voltage_max[0], GL518_REG_VDD_LIMIT);
|
||||
set_high(IN, in_max1, voltage_max[1], GL518_REG_VIN1_LIMIT);
|
||||
set_high(IN, in_max2, voltage_max[2], GL518_REG_VIN2_LIMIT);
|
||||
set_high(IN, in_max3, voltage_max[3], GL518_REG_VIN3_LIMIT);
|
||||
set_bits(BOOL, beep_enable, beep_enable, GL518_REG_CONF, 0x04, 2);
|
||||
set(BEEP_MASK, beep_mask, beep_mask, GL518_REG_ALARM);
|
||||
|
||||
static ssize_t set_fan_min1(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct gl518_data *data = i2c_get_clientdata(client);
|
||||
int regvalue;
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
regvalue = gl518_read_value(client, GL518_REG_FAN_LIMIT);
|
||||
data->fan_min[0] = FAN_TO_REG(val,
|
||||
DIV_FROM_REG(data->fan_div[0]));
|
||||
regvalue = (regvalue & 0x00ff) | (data->fan_min[0] << 8);
|
||||
gl518_write_value(client, GL518_REG_FAN_LIMIT, regvalue);
|
||||
|
||||
data->beep_mask = gl518_read_value(client, GL518_REG_ALARM);
|
||||
if (data->fan_min[0] == 0)
|
||||
data->alarm_mask &= ~0x20;
|
||||
else
|
||||
data->alarm_mask |= 0x20;
|
||||
data->beep_mask &= data->alarm_mask;
|
||||
gl518_write_value(client, GL518_REG_ALARM, data->beep_mask);
|
||||
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_fan_min2(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct gl518_data *data = i2c_get_clientdata(client);
|
||||
int regvalue;
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
regvalue = gl518_read_value(client, GL518_REG_FAN_LIMIT);
|
||||
data->fan_min[1] = FAN_TO_REG(val,
|
||||
DIV_FROM_REG(data->fan_div[1]));
|
||||
regvalue = (regvalue & 0xff00) | data->fan_min[1];
|
||||
gl518_write_value(client, GL518_REG_FAN_LIMIT, regvalue);
|
||||
|
||||
data->beep_mask = gl518_read_value(client, GL518_REG_ALARM);
|
||||
if (data->fan_min[1] == 0)
|
||||
data->alarm_mask &= ~0x40;
|
||||
else
|
||||
data->alarm_mask |= 0x40;
|
||||
data->beep_mask &= data->alarm_mask;
|
||||
gl518_write_value(client, GL518_REG_ALARM, data->beep_mask);
|
||||
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input1, NULL);
|
||||
static DEVICE_ATTR(temp1_max, S_IWUSR|S_IRUGO, show_temp_max1, set_temp_max1);
|
||||
static DEVICE_ATTR(temp1_max_hyst, S_IWUSR|S_IRUGO,
|
||||
show_temp_hyst1, set_temp_hyst1);
|
||||
static DEVICE_ATTR(fan1_auto, S_IWUSR|S_IRUGO, show_fan_auto1, set_fan_auto1);
|
||||
static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input1, NULL);
|
||||
static DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input2, NULL);
|
||||
static DEVICE_ATTR(fan1_min, S_IWUSR|S_IRUGO, show_fan_min1, set_fan_min1);
|
||||
static DEVICE_ATTR(fan2_min, S_IWUSR|S_IRUGO, show_fan_min2, set_fan_min2);
|
||||
static DEVICE_ATTR(fan1_div, S_IWUSR|S_IRUGO, show_fan_div1, set_fan_div1);
|
||||
static DEVICE_ATTR(fan2_div, S_IWUSR|S_IRUGO, show_fan_div2, set_fan_div2);
|
||||
static DEVICE_ATTR(in0_input, S_IRUGO, show_in_input0, NULL);
|
||||
static DEVICE_ATTR(in1_input, S_IRUGO, show_in_input1, NULL);
|
||||
static DEVICE_ATTR(in2_input, S_IRUGO, show_in_input2, NULL);
|
||||
static DEVICE_ATTR(in3_input, S_IRUGO, show_in_input3, NULL);
|
||||
static DEVICE_ATTR(in0_min, S_IWUSR|S_IRUGO, show_in_min0, set_in_min0);
|
||||
static DEVICE_ATTR(in1_min, S_IWUSR|S_IRUGO, show_in_min1, set_in_min1);
|
||||
static DEVICE_ATTR(in2_min, S_IWUSR|S_IRUGO, show_in_min2, set_in_min2);
|
||||
static DEVICE_ATTR(in3_min, S_IWUSR|S_IRUGO, show_in_min3, set_in_min3);
|
||||
static DEVICE_ATTR(in0_max, S_IWUSR|S_IRUGO, show_in_max0, set_in_max0);
|
||||
static DEVICE_ATTR(in1_max, S_IWUSR|S_IRUGO, show_in_max1, set_in_max1);
|
||||
static DEVICE_ATTR(in2_max, S_IWUSR|S_IRUGO, show_in_max2, set_in_max2);
|
||||
static DEVICE_ATTR(in3_max, S_IWUSR|S_IRUGO, show_in_max3, set_in_max3);
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
static DEVICE_ATTR(beep_enable, S_IWUSR|S_IRUGO,
|
||||
show_beep_enable, set_beep_enable);
|
||||
static DEVICE_ATTR(beep_mask, S_IWUSR|S_IRUGO,
|
||||
show_beep_mask, set_beep_mask);
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static int gl518_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, gl518_detect);
|
||||
}
|
||||
|
||||
static int gl518_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
int i;
|
||||
struct i2c_client *new_client;
|
||||
struct gl518_data *data;
|
||||
int err = 0;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
goto exit;
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
client structure, even though we cannot fill it completely yet.
|
||||
But it allows us to access gl518_{read,write}_value. */
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct gl518_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct gl518_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &gl518_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Now, we do the remaining detection. */
|
||||
|
||||
if (kind < 0) {
|
||||
if ((gl518_read_value(new_client, GL518_REG_CHIP_ID) != 0x80)
|
||||
|| (gl518_read_value(new_client, GL518_REG_CONF) & 0x80))
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Determine the chip type. */
|
||||
if (kind <= 0) {
|
||||
i = gl518_read_value(new_client, GL518_REG_REVISION);
|
||||
if (i == 0x00) {
|
||||
kind = gl518sm_r00;
|
||||
} else if (i == 0x80) {
|
||||
kind = gl518sm_r80;
|
||||
} else {
|
||||
if (kind <= 0)
|
||||
dev_info(&adapter->dev,
|
||||
"Ignoring 'force' parameter for unknown "
|
||||
"chip at adapter %d, address 0x%02x\n",
|
||||
i2c_adapter_id(adapter), address);
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields */
|
||||
strlcpy(new_client->name, "gl518sm", I2C_NAME_SIZE);
|
||||
data->type = kind;
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Initialize the GL518SM chip */
|
||||
data->alarm_mask = 0xff;
|
||||
data->voltage_in[0]=data->voltage_in[1]=data->voltage_in[2]=0;
|
||||
gl518_init_client((struct i2c_client *) new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_auto);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
device_create_file(&new_client->dev, &dev_attr_beep_enable);
|
||||
device_create_file(&new_client->dev, &dev_attr_beep_mask);
|
||||
|
||||
return 0;
|
||||
|
||||
/* OK, this is not exactly good programming practice, usually. But it is
|
||||
very code-efficient in this case. */
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* Called when we have found a new GL518SM.
|
||||
Note that we preserve D4:NoFan2 and D2:beep_enable. */
|
||||
static void gl518_init_client(struct i2c_client *client)
|
||||
{
|
||||
/* Make sure we leave D7:Reset untouched */
|
||||
u8 regvalue = gl518_read_value(client, GL518_REG_CONF) & 0x7f;
|
||||
|
||||
/* Comparator mode (D3=0), standby mode (D6=0) */
|
||||
gl518_write_value(client, GL518_REG_CONF, (regvalue &= 0x37));
|
||||
|
||||
/* Never interrupts */
|
||||
gl518_write_value(client, GL518_REG_MASK, 0x00);
|
||||
|
||||
/* Clear status register (D5=1), start (D6=1) */
|
||||
gl518_write_value(client, GL518_REG_CONF, 0x20 | regvalue);
|
||||
gl518_write_value(client, GL518_REG_CONF, 0x40 | regvalue);
|
||||
}
|
||||
|
||||
static int gl518_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, "
|
||||
"client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Registers 0x07 to 0x0c are word-sized, others are byte-sized
|
||||
GL518 uses a high-byte first convention, which is exactly opposite to
|
||||
the usual practice. */
|
||||
static int gl518_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
if ((reg >= 0x07) && (reg <= 0x0c))
|
||||
return swab16(i2c_smbus_read_word_data(client, reg));
|
||||
else
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
/* Registers 0x07 to 0x0c are word-sized, others are byte-sized
|
||||
GL518 uses a high-byte first convention, which is exactly opposite to
|
||||
the usual practice. */
|
||||
static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value)
|
||||
{
|
||||
if ((reg >= 0x07) && (reg <= 0x0c))
|
||||
return i2c_smbus_write_word_data(client, reg, swab16(value));
|
||||
else
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
static struct gl518_data *gl518_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct gl518_data *data = i2c_get_clientdata(client);
|
||||
int val;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
dev_dbg(&client->dev, "Starting gl518 update\n");
|
||||
|
||||
data->alarms = gl518_read_value(client, GL518_REG_INT);
|
||||
data->beep_mask = gl518_read_value(client, GL518_REG_ALARM);
|
||||
|
||||
val = gl518_read_value(client, GL518_REG_VDD_LIMIT);
|
||||
data->voltage_min[0] = val & 0xff;
|
||||
data->voltage_max[0] = (val >> 8) & 0xff;
|
||||
val = gl518_read_value(client, GL518_REG_VIN1_LIMIT);
|
||||
data->voltage_min[1] = val & 0xff;
|
||||
data->voltage_max[1] = (val >> 8) & 0xff;
|
||||
val = gl518_read_value(client, GL518_REG_VIN2_LIMIT);
|
||||
data->voltage_min[2] = val & 0xff;
|
||||
data->voltage_max[2] = (val >> 8) & 0xff;
|
||||
val = gl518_read_value(client, GL518_REG_VIN3_LIMIT);
|
||||
data->voltage_min[3] = val & 0xff;
|
||||
data->voltage_max[3] = (val >> 8) & 0xff;
|
||||
|
||||
val = gl518_read_value(client, GL518_REG_FAN_COUNT);
|
||||
data->fan_in[0] = (val >> 8) & 0xff;
|
||||
data->fan_in[1] = val & 0xff;
|
||||
|
||||
val = gl518_read_value(client, GL518_REG_FAN_LIMIT);
|
||||
data->fan_min[0] = (val >> 8) & 0xff;
|
||||
data->fan_min[1] = val & 0xff;
|
||||
|
||||
data->temp_in = gl518_read_value(client, GL518_REG_TEMP_IN);
|
||||
data->temp_max =
|
||||
gl518_read_value(client, GL518_REG_TEMP_MAX);
|
||||
data->temp_hyst =
|
||||
gl518_read_value(client, GL518_REG_TEMP_HYST);
|
||||
|
||||
val = gl518_read_value(client, GL518_REG_MISC);
|
||||
data->fan_div[0] = (val >> 6) & 0x03;
|
||||
data->fan_div[1] = (val >> 4) & 0x03;
|
||||
data->fan_auto1 = (val >> 3) & 0x01;
|
||||
|
||||
data->alarms &= data->alarm_mask;
|
||||
|
||||
val = gl518_read_value(client, GL518_REG_CONF);
|
||||
data->beep_enable = (val >> 2) & 1;
|
||||
|
||||
if (data->type != gl518sm_r00) {
|
||||
data->voltage_in[0] =
|
||||
gl518_read_value(client, GL518_REG_VDD);
|
||||
data->voltage_in[1] =
|
||||
gl518_read_value(client, GL518_REG_VIN1);
|
||||
data->voltage_in[2] =
|
||||
gl518_read_value(client, GL518_REG_VIN2);
|
||||
}
|
||||
data->voltage_in[3] =
|
||||
gl518_read_value(client, GL518_REG_VIN3);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_gl518sm_init(void)
|
||||
{
|
||||
return i2c_add_driver(&gl518_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_gl518sm_exit(void)
|
||||
{
|
||||
i2c_del_driver(&gl518_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, "
|
||||
"Kyosti Malkki <kmalkki@cc.hut.fi> and "
|
||||
"Hong-Gunn Chew <hglinux@gunnet.org>");
|
||||
MODULE_DESCRIPTION("GL518SM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_gl518sm_init);
|
||||
module_exit(sensors_gl518sm_exit);
|
||||
769
drivers/hwmon/gl520sm.c
Normal file
769
drivers/hwmon/gl520sm.c
Normal file
|
|
@ -0,0 +1,769 @@
|
|||
/*
|
||||
gl520sm.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>,
|
||||
Kyösti Mälkki <kmalkki@cc.hut.fi>
|
||||
Copyright (c) 2005 Maarten Deprez <maartendeprez@users.sourceforge.net>
|
||||
|
||||
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/i2c.h>
|
||||
#include <linux/i2c-sensor.h>
|
||||
#include <linux/i2c-vid.h>
|
||||
|
||||
/* Type of the extra sensor */
|
||||
static unsigned short extra_sensor_type;
|
||||
module_param(extra_sensor_type, ushort, 0);
|
||||
MODULE_PARM_DESC(extra_sensor_type, "Type of extra sensor (0=autodetect, 1=temperature, 2=voltage)");
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_1(gl520sm);
|
||||
|
||||
/* Many GL520 constants specified below
|
||||
One of the inputs can be configured as either temp or voltage.
|
||||
That's why _TEMP2 and _IN4 access the same register
|
||||
*/
|
||||
|
||||
/* The GL520 registers */
|
||||
#define GL520_REG_CHIP_ID 0x00
|
||||
#define GL520_REG_REVISION 0x01
|
||||
#define GL520_REG_CONF 0x03
|
||||
#define GL520_REG_MASK 0x11
|
||||
|
||||
#define GL520_REG_VID_INPUT 0x02
|
||||
|
||||
#define GL520_REG_IN0_INPUT 0x15
|
||||
#define GL520_REG_IN0_LIMIT 0x0c
|
||||
#define GL520_REG_IN0_MIN GL520_REG_IN0_LIMIT
|
||||
#define GL520_REG_IN0_MAX GL520_REG_IN0_LIMIT
|
||||
|
||||
#define GL520_REG_IN1_INPUT 0x14
|
||||
#define GL520_REG_IN1_LIMIT 0x09
|
||||
#define GL520_REG_IN1_MIN GL520_REG_IN1_LIMIT
|
||||
#define GL520_REG_IN1_MAX GL520_REG_IN1_LIMIT
|
||||
|
||||
#define GL520_REG_IN2_INPUT 0x13
|
||||
#define GL520_REG_IN2_LIMIT 0x0a
|
||||
#define GL520_REG_IN2_MIN GL520_REG_IN2_LIMIT
|
||||
#define GL520_REG_IN2_MAX GL520_REG_IN2_LIMIT
|
||||
|
||||
#define GL520_REG_IN3_INPUT 0x0d
|
||||
#define GL520_REG_IN3_LIMIT 0x0b
|
||||
#define GL520_REG_IN3_MIN GL520_REG_IN3_LIMIT
|
||||
#define GL520_REG_IN3_MAX GL520_REG_IN3_LIMIT
|
||||
|
||||
#define GL520_REG_IN4_INPUT 0x0e
|
||||
#define GL520_REG_IN4_MAX 0x17
|
||||
#define GL520_REG_IN4_MIN 0x18
|
||||
|
||||
#define GL520_REG_TEMP1_INPUT 0x04
|
||||
#define GL520_REG_TEMP1_MAX 0x05
|
||||
#define GL520_REG_TEMP1_MAX_HYST 0x06
|
||||
|
||||
#define GL520_REG_TEMP2_INPUT 0x0e
|
||||
#define GL520_REG_TEMP2_MAX 0x17
|
||||
#define GL520_REG_TEMP2_MAX_HYST 0x18
|
||||
|
||||
#define GL520_REG_FAN_INPUT 0x07
|
||||
#define GL520_REG_FAN_MIN 0x08
|
||||
#define GL520_REG_FAN_DIV 0x0f
|
||||
#define GL520_REG_FAN_OFF GL520_REG_FAN_DIV
|
||||
|
||||
#define GL520_REG_ALARMS 0x12
|
||||
#define GL520_REG_BEEP_MASK 0x10
|
||||
#define GL520_REG_BEEP_ENABLE GL520_REG_CONF
|
||||
|
||||
/*
|
||||
* Function declarations
|
||||
*/
|
||||
|
||||
static int gl520_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int gl520_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void gl520_init_client(struct i2c_client *client);
|
||||
static int gl520_detach_client(struct i2c_client *client);
|
||||
static int gl520_read_value(struct i2c_client *client, u8 reg);
|
||||
static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value);
|
||||
static struct gl520_data *gl520_update_device(struct device *dev);
|
||||
|
||||
/* Driver data */
|
||||
static struct i2c_driver gl520_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "gl520sm",
|
||||
.id = I2C_DRIVERID_GL520,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = gl520_attach_adapter,
|
||||
.detach_client = gl520_detach_client,
|
||||
};
|
||||
|
||||
/* Client data */
|
||||
struct gl520_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid; /* zero until the following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
|
||||
u8 vid;
|
||||
u8 vrm;
|
||||
u8 in_input[5]; /* [0] = VVD */
|
||||
u8 in_min[5]; /* [0] = VDD */
|
||||
u8 in_max[5]; /* [0] = VDD */
|
||||
u8 fan_input[2];
|
||||
u8 fan_min[2];
|
||||
u8 fan_div[2];
|
||||
u8 fan_off;
|
||||
u8 temp_input[2];
|
||||
u8 temp_max[2];
|
||||
u8 temp_max_hyst[2];
|
||||
u8 alarms;
|
||||
u8 beep_enable;
|
||||
u8 beep_mask;
|
||||
u8 alarm_mask;
|
||||
u8 two_temps;
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
#define sysfs_r(type, n, item, reg) \
|
||||
static ssize_t get_##type##item (struct gl520_data *, char *, int); \
|
||||
static ssize_t get_##type##n##item (struct device *, struct device_attribute *attr, char *); \
|
||||
static ssize_t get_##type##n##item (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct gl520_data *data = gl520_update_device(dev); \
|
||||
return get_##type##item(data, buf, (n)); \
|
||||
}
|
||||
|
||||
#define sysfs_w(type, n, item, reg) \
|
||||
static ssize_t set_##type##item (struct i2c_client *, struct gl520_data *, const char *, size_t, int, int); \
|
||||
static ssize_t set_##type##n##item (struct device *, struct device_attribute *attr, const char *, size_t); \
|
||||
static ssize_t set_##type##n##item (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct gl520_data *data = i2c_get_clientdata(client); \
|
||||
return set_##type##item(client, data, buf, count, (n), reg); \
|
||||
}
|
||||
|
||||
#define sysfs_rw_n(type, n, item, reg) \
|
||||
sysfs_r(type, n, item, reg) \
|
||||
sysfs_w(type, n, item, reg) \
|
||||
static DEVICE_ATTR(type##n##item, S_IRUGO | S_IWUSR, get_##type##n##item, set_##type##n##item);
|
||||
|
||||
#define sysfs_ro_n(type, n, item, reg) \
|
||||
sysfs_r(type, n, item, reg) \
|
||||
static DEVICE_ATTR(type##n##item, S_IRUGO, get_##type##n##item, NULL);
|
||||
|
||||
#define sysfs_rw(type, item, reg) \
|
||||
sysfs_r(type, 0, item, reg) \
|
||||
sysfs_w(type, 0, item, reg) \
|
||||
static DEVICE_ATTR(type##item, S_IRUGO | S_IWUSR, get_##type##0##item, set_##type##0##item);
|
||||
|
||||
#define sysfs_ro(type, item, reg) \
|
||||
sysfs_r(type, 0, item, reg) \
|
||||
static DEVICE_ATTR(type##item, S_IRUGO, get_##type##0##item, NULL);
|
||||
|
||||
|
||||
#define sysfs_vid(n) \
|
||||
sysfs_ro_n(cpu, n, _vid, GL520_REG_VID_INPUT)
|
||||
|
||||
#define device_create_file_vid(client, n) \
|
||||
device_create_file(&client->dev, &dev_attr_cpu##n##_vid)
|
||||
|
||||
#define sysfs_in(n) \
|
||||
sysfs_ro_n(in, n, _input, GL520_REG_IN##n##INPUT) \
|
||||
sysfs_rw_n(in, n, _min, GL520_REG_IN##n##_MIN) \
|
||||
sysfs_rw_n(in, n, _max, GL520_REG_IN##n##_MAX) \
|
||||
|
||||
#define device_create_file_in(client, n) \
|
||||
({device_create_file(&client->dev, &dev_attr_in##n##_input); \
|
||||
device_create_file(&client->dev, &dev_attr_in##n##_min); \
|
||||
device_create_file(&client->dev, &dev_attr_in##n##_max);})
|
||||
|
||||
#define sysfs_fan(n) \
|
||||
sysfs_ro_n(fan, n, _input, GL520_REG_FAN_INPUT) \
|
||||
sysfs_rw_n(fan, n, _min, GL520_REG_FAN_MIN) \
|
||||
sysfs_rw_n(fan, n, _div, GL520_REG_FAN_DIV)
|
||||
|
||||
#define device_create_file_fan(client, n) \
|
||||
({device_create_file(&client->dev, &dev_attr_fan##n##_input); \
|
||||
device_create_file(&client->dev, &dev_attr_fan##n##_min); \
|
||||
device_create_file(&client->dev, &dev_attr_fan##n##_div);})
|
||||
|
||||
#define sysfs_fan_off(n) \
|
||||
sysfs_rw_n(fan, n, _off, GL520_REG_FAN_OFF) \
|
||||
|
||||
#define device_create_file_fan_off(client, n) \
|
||||
device_create_file(&client->dev, &dev_attr_fan##n##_off)
|
||||
|
||||
#define sysfs_temp(n) \
|
||||
sysfs_ro_n(temp, n, _input, GL520_REG_TEMP##n##_INPUT) \
|
||||
sysfs_rw_n(temp, n, _max, GL520_REG_TEMP##n##_MAX) \
|
||||
sysfs_rw_n(temp, n, _max_hyst, GL520_REG_TEMP##n##_MAX_HYST)
|
||||
|
||||
#define device_create_file_temp(client, n) \
|
||||
({device_create_file(&client->dev, &dev_attr_temp##n##_input); \
|
||||
device_create_file(&client->dev, &dev_attr_temp##n##_max); \
|
||||
device_create_file(&client->dev, &dev_attr_temp##n##_max_hyst);})
|
||||
|
||||
#define sysfs_alarms() \
|
||||
sysfs_ro(alarms, , GL520_REG_ALARMS) \
|
||||
sysfs_rw(beep_enable, , GL520_REG_BEEP_ENABLE) \
|
||||
sysfs_rw(beep_mask, , GL520_REG_BEEP_MASK)
|
||||
|
||||
#define device_create_file_alarms(client) \
|
||||
({device_create_file(&client->dev, &dev_attr_alarms); \
|
||||
device_create_file(&client->dev, &dev_attr_beep_enable); \
|
||||
device_create_file(&client->dev, &dev_attr_beep_mask);})
|
||||
|
||||
|
||||
sysfs_vid(0)
|
||||
|
||||
sysfs_in(0)
|
||||
sysfs_in(1)
|
||||
sysfs_in(2)
|
||||
sysfs_in(3)
|
||||
sysfs_in(4)
|
||||
|
||||
sysfs_fan(1)
|
||||
sysfs_fan(2)
|
||||
sysfs_fan_off(1)
|
||||
|
||||
sysfs_temp(1)
|
||||
sysfs_temp(2)
|
||||
|
||||
sysfs_alarms()
|
||||
|
||||
|
||||
static ssize_t get_cpu_vid(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%u\n", vid_from_reg(data->vid, data->vrm));
|
||||
}
|
||||
|
||||
#define VDD_FROM_REG(val) (((val)*95+2)/4)
|
||||
#define VDD_TO_REG(val) (SENSORS_LIMIT((((val)*4+47)/95),0,255))
|
||||
|
||||
#define IN_FROM_REG(val) ((val)*19)
|
||||
#define IN_TO_REG(val) (SENSORS_LIMIT((((val)+9)/19),0,255))
|
||||
|
||||
static ssize_t get_in_input(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
u8 r = data->in_input[n];
|
||||
|
||||
if (n == 0)
|
||||
return sprintf(buf, "%d\n", VDD_FROM_REG(r));
|
||||
else
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(r));
|
||||
}
|
||||
|
||||
static ssize_t get_in_min(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
u8 r = data->in_min[n];
|
||||
|
||||
if (n == 0)
|
||||
return sprintf(buf, "%d\n", VDD_FROM_REG(r));
|
||||
else
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(r));
|
||||
}
|
||||
|
||||
static ssize_t get_in_max(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
u8 r = data->in_max[n];
|
||||
|
||||
if (n == 0)
|
||||
return sprintf(buf, "%d\n", VDD_FROM_REG(r));
|
||||
else
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(r));
|
||||
}
|
||||
|
||||
static ssize_t set_in_min(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
long v = simple_strtol(buf, NULL, 10);
|
||||
u8 r;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (n == 0)
|
||||
r = VDD_TO_REG(v);
|
||||
else
|
||||
r = IN_TO_REG(v);
|
||||
|
||||
data->in_min[n] = r;
|
||||
|
||||
if (n < 4)
|
||||
gl520_write_value(client, reg, (gl520_read_value(client, reg) & ~0xff) | r);
|
||||
else
|
||||
gl520_write_value(client, reg, r);
|
||||
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_in_max(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
long v = simple_strtol(buf, NULL, 10);
|
||||
u8 r;
|
||||
|
||||
if (n == 0)
|
||||
r = VDD_TO_REG(v);
|
||||
else
|
||||
r = IN_TO_REG(v);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
data->in_max[n] = r;
|
||||
|
||||
if (n < 4)
|
||||
gl520_write_value(client, reg, (gl520_read_value(client, reg) & ~0xff00) | (r << 8));
|
||||
else
|
||||
gl520_write_value(client, reg, r);
|
||||
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define DIV_FROM_REG(val) (1 << (val))
|
||||
#define FAN_FROM_REG(val,div) ((val)==0 ? 0 : (480000/((val) << (div))))
|
||||
#define FAN_TO_REG(val,div) ((val)<=0?0:SENSORS_LIMIT((480000 + ((val) << ((div)-1))) / ((val) << (div)), 1, 255));
|
||||
|
||||
static ssize_t get_fan_input(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_input[n - 1], data->fan_div[n - 1]));
|
||||
}
|
||||
|
||||
static ssize_t get_fan_min(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[n - 1], data->fan_div[n - 1]));
|
||||
}
|
||||
|
||||
static ssize_t get_fan_div(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[n - 1]));
|
||||
}
|
||||
|
||||
static ssize_t get_fan_off(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", data->fan_off);
|
||||
}
|
||||
|
||||
static ssize_t set_fan_min(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10);
|
||||
u8 r;
|
||||
|
||||
down(&data->update_lock);
|
||||
r = FAN_TO_REG(v, data->fan_div[n - 1]);
|
||||
data->fan_min[n - 1] = r;
|
||||
|
||||
if (n == 1)
|
||||
gl520_write_value(client, reg, (gl520_read_value(client, reg) & ~0xff00) | (r << 8));
|
||||
else
|
||||
gl520_write_value(client, reg, (gl520_read_value(client, reg) & ~0xff) | r);
|
||||
|
||||
data->beep_mask = gl520_read_value(client, GL520_REG_BEEP_MASK);
|
||||
if (data->fan_min[n - 1] == 0)
|
||||
data->alarm_mask &= (n == 1) ? ~0x20 : ~0x40;
|
||||
else
|
||||
data->alarm_mask |= (n == 1) ? 0x20 : 0x40;
|
||||
data->beep_mask &= data->alarm_mask;
|
||||
gl520_write_value(client, GL520_REG_BEEP_MASK, data->beep_mask);
|
||||
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_fan_div(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
unsigned long v = simple_strtoul(buf, NULL, 10);
|
||||
u8 r;
|
||||
|
||||
switch (v) {
|
||||
case 1: r = 0; break;
|
||||
case 2: r = 1; break;
|
||||
case 4: r = 2; break;
|
||||
case 8: r = 3; break;
|
||||
default:
|
||||
dev_err(&client->dev, "fan_div value %ld not supported. Choose one of 1, 2, 4 or 8!\n", v);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
down(&data->update_lock);
|
||||
data->fan_div[n - 1] = r;
|
||||
|
||||
if (n == 1)
|
||||
gl520_write_value(client, reg, (gl520_read_value(client, reg) & ~0xc0) | (r << 6));
|
||||
else
|
||||
gl520_write_value(client, reg, (gl520_read_value(client, reg) & ~0x30) | (r << 4));
|
||||
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_fan_off(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
u8 r = simple_strtoul(buf, NULL, 10)?1:0;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->fan_off = r;
|
||||
gl520_write_value(client, reg, (gl520_read_value(client, reg) & ~0x0c) | (r << 2));
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define TEMP_FROM_REG(val) (((val) - 130) * 1000)
|
||||
#define TEMP_TO_REG(val) (SENSORS_LIMIT(((((val)<0?(val)-500:(val)+500) / 1000)+130),0,255))
|
||||
|
||||
static ssize_t get_temp_input(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_input[n - 1]));
|
||||
}
|
||||
|
||||
static ssize_t get_temp_max(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[n - 1]));
|
||||
}
|
||||
|
||||
static ssize_t get_temp_max_hyst(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max_hyst[n - 1]));
|
||||
}
|
||||
|
||||
static ssize_t set_temp_max(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
long v = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp_max[n - 1] = TEMP_TO_REG(v);;
|
||||
gl520_write_value(client, reg, data->temp_max[n - 1]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_temp_max_hyst(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
long v = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp_max_hyst[n - 1] = TEMP_TO_REG(v);
|
||||
gl520_write_value(client, reg, data->temp_max_hyst[n - 1]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t get_alarms(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", data->alarms);
|
||||
}
|
||||
|
||||
static ssize_t get_beep_enable(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", data->beep_enable);
|
||||
}
|
||||
|
||||
static ssize_t get_beep_mask(struct gl520_data *data, char *buf, int n)
|
||||
{
|
||||
return sprintf(buf, "%d\n", data->beep_mask);
|
||||
}
|
||||
|
||||
static ssize_t set_beep_enable(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
u8 r = simple_strtoul(buf, NULL, 10)?0:1;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->beep_enable = !r;
|
||||
gl520_write_value(client, reg, (gl520_read_value(client, reg) & ~0x04) | (r << 2));
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_beep_mask(struct i2c_client *client, struct gl520_data *data, const char *buf, size_t count, int n, int reg)
|
||||
{
|
||||
u8 r = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
r &= data->alarm_mask;
|
||||
data->beep_mask = r;
|
||||
gl520_write_value(client, reg, r);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static int gl520_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, gl520_detect);
|
||||
}
|
||||
|
||||
static int gl520_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct gl520_data *data;
|
||||
int err = 0;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
goto exit;
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
client structure, even though we cannot fill it completely yet.
|
||||
But it allows us to access gl520_{read,write}_value. */
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct gl520_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct gl520_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &gl520_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Determine the chip type. */
|
||||
if (kind < 0) {
|
||||
if ((gl520_read_value(new_client, GL520_REG_CHIP_ID) != 0x20) ||
|
||||
((gl520_read_value(new_client, GL520_REG_REVISION) & 0x7f) != 0x00) ||
|
||||
((gl520_read_value(new_client, GL520_REG_CONF) & 0x80) != 0x00)) {
|
||||
dev_dbg(&new_client->dev, "Unknown chip type, skipping\n");
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields */
|
||||
strlcpy(new_client->name, "gl520sm", I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Initialize the GL520SM chip */
|
||||
gl520_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file_vid(new_client, 0);
|
||||
|
||||
device_create_file_in(new_client, 0);
|
||||
device_create_file_in(new_client, 1);
|
||||
device_create_file_in(new_client, 2);
|
||||
device_create_file_in(new_client, 3);
|
||||
if (!data->two_temps)
|
||||
device_create_file_in(new_client, 4);
|
||||
|
||||
device_create_file_fan(new_client, 1);
|
||||
device_create_file_fan(new_client, 2);
|
||||
device_create_file_fan_off(new_client, 1);
|
||||
|
||||
device_create_file_temp(new_client, 1);
|
||||
if (data->two_temps)
|
||||
device_create_file_temp(new_client, 2);
|
||||
|
||||
device_create_file_alarms(new_client);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* Called when we have found a new GL520SM. */
|
||||
static void gl520_init_client(struct i2c_client *client)
|
||||
{
|
||||
struct gl520_data *data = i2c_get_clientdata(client);
|
||||
u8 oldconf, conf;
|
||||
|
||||
conf = oldconf = gl520_read_value(client, GL520_REG_CONF);
|
||||
|
||||
data->alarm_mask = 0xff;
|
||||
data->vrm = i2c_which_vrm();
|
||||
|
||||
if (extra_sensor_type == 1)
|
||||
conf &= ~0x10;
|
||||
else if (extra_sensor_type == 2)
|
||||
conf |= 0x10;
|
||||
data->two_temps = !(conf & 0x10);
|
||||
|
||||
/* If IRQ# is disabled, we can safely force comparator mode */
|
||||
if (!(conf & 0x20))
|
||||
conf &= 0xf7;
|
||||
|
||||
/* Enable monitoring if needed */
|
||||
conf |= 0x40;
|
||||
|
||||
if (conf != oldconf)
|
||||
gl520_write_value(client, GL520_REG_CONF, conf);
|
||||
|
||||
gl520_update_device(&(client->dev));
|
||||
|
||||
if (data->fan_min[0] == 0)
|
||||
data->alarm_mask &= ~0x20;
|
||||
if (data->fan_min[1] == 0)
|
||||
data->alarm_mask &= ~0x40;
|
||||
|
||||
data->beep_mask &= data->alarm_mask;
|
||||
gl520_write_value(client, GL520_REG_BEEP_MASK, data->beep_mask);
|
||||
}
|
||||
|
||||
static int gl520_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, "
|
||||
"client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Registers 0x07 to 0x0c are word-sized, others are byte-sized
|
||||
GL520 uses a high-byte first convention */
|
||||
static int gl520_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
if ((reg >= 0x07) && (reg <= 0x0c))
|
||||
return swab16(i2c_smbus_read_word_data(client, reg));
|
||||
else
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value)
|
||||
{
|
||||
if ((reg >= 0x07) && (reg <= 0x0c))
|
||||
return i2c_smbus_write_word_data(client, reg, swab16(value));
|
||||
else
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
|
||||
static struct gl520_data *gl520_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct gl520_data *data = i2c_get_clientdata(client);
|
||||
int val;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if ((jiffies - data->last_updated > 2 * HZ) ||
|
||||
(jiffies < data->last_updated) || !data->valid) {
|
||||
|
||||
dev_dbg(&client->dev, "Starting gl520sm update\n");
|
||||
|
||||
data->alarms = gl520_read_value(client, GL520_REG_ALARMS);
|
||||
data->beep_mask = gl520_read_value(client, GL520_REG_BEEP_MASK);
|
||||
data->vid = gl520_read_value(client, GL520_REG_VID_INPUT) & 0x1f;
|
||||
|
||||
val = gl520_read_value(client, GL520_REG_IN0_LIMIT);
|
||||
data->in_min[0] = val & 0xff;
|
||||
data->in_max[0] = (val >> 8) & 0xff;
|
||||
val = gl520_read_value(client, GL520_REG_IN1_LIMIT);
|
||||
data->in_min[1] = val & 0xff;
|
||||
data->in_max[1] = (val >> 8) & 0xff;
|
||||
val = gl520_read_value(client, GL520_REG_IN2_LIMIT);
|
||||
data->in_min[2] = val & 0xff;
|
||||
data->in_max[2] = (val >> 8) & 0xff;
|
||||
val = gl520_read_value(client, GL520_REG_IN3_LIMIT);
|
||||
data->in_min[3] = val & 0xff;
|
||||
data->in_max[3] = (val >> 8) & 0xff;
|
||||
|
||||
val = gl520_read_value(client, GL520_REG_FAN_INPUT);
|
||||
data->fan_input[0] = (val >> 8) & 0xff;
|
||||
data->fan_input[1] = val & 0xff;
|
||||
|
||||
val = gl520_read_value(client, GL520_REG_FAN_MIN);
|
||||
data->fan_min[0] = (val >> 8) & 0xff;
|
||||
data->fan_min[1] = val & 0xff;
|
||||
|
||||
data->temp_input[0] = gl520_read_value(client, GL520_REG_TEMP1_INPUT);
|
||||
data->temp_max[0] = gl520_read_value(client, GL520_REG_TEMP1_MAX);
|
||||
data->temp_max_hyst[0] = gl520_read_value(client, GL520_REG_TEMP1_MAX_HYST);
|
||||
|
||||
val = gl520_read_value(client, GL520_REG_FAN_DIV);
|
||||
data->fan_div[0] = (val >> 6) & 0x03;
|
||||
data->fan_div[1] = (val >> 4) & 0x03;
|
||||
data->fan_off = (val >> 2) & 0x01;
|
||||
|
||||
data->alarms &= data->alarm_mask;
|
||||
|
||||
val = gl520_read_value(client, GL520_REG_CONF);
|
||||
data->beep_enable = !((val >> 2) & 1);
|
||||
|
||||
data->in_input[0] = gl520_read_value(client, GL520_REG_IN0_INPUT);
|
||||
data->in_input[1] = gl520_read_value(client, GL520_REG_IN1_INPUT);
|
||||
data->in_input[2] = gl520_read_value(client, GL520_REG_IN2_INPUT);
|
||||
data->in_input[3] = gl520_read_value(client, GL520_REG_IN3_INPUT);
|
||||
|
||||
/* Temp1 and Vin4 are the same input */
|
||||
if (data->two_temps) {
|
||||
data->temp_input[1] = gl520_read_value(client, GL520_REG_TEMP2_INPUT);
|
||||
data->temp_max[1] = gl520_read_value(client, GL520_REG_TEMP2_MAX);
|
||||
data->temp_max_hyst[1] = gl520_read_value(client, GL520_REG_TEMP2_MAX_HYST);
|
||||
} else {
|
||||
data->in_input[4] = gl520_read_value(client, GL520_REG_IN4_INPUT);
|
||||
data->in_min[4] = gl520_read_value(client, GL520_REG_IN4_MIN);
|
||||
data->in_max[4] = gl520_read_value(client, GL520_REG_IN4_MAX);
|
||||
}
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
static int __init sensors_gl520sm_init(void)
|
||||
{
|
||||
return i2c_add_driver(&gl520_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_gl520sm_exit(void)
|
||||
{
|
||||
i2c_del_driver(&gl520_driver);
|
||||
}
|
||||
|
||||
|
||||
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, "
|
||||
"Kyösti Mälkki <kmalkki@cc.hut.fi>, "
|
||||
"Maarten Deprez <maartendeprez@users.sourceforge.net>");
|
||||
MODULE_DESCRIPTION("GL520SM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_gl520sm_init);
|
||||
module_exit(sensors_gl520sm_exit);
|
||||
1184
drivers/hwmon/it87.c
Normal file
1184
drivers/hwmon/it87.c
Normal file
File diff suppressed because it is too large
Load diff
597
drivers/hwmon/lm63.c
Normal file
597
drivers/hwmon/lm63.c
Normal file
|
|
@ -0,0 +1,597 @@
|
|||
/*
|
||||
* lm63.c - driver for the National Semiconductor LM63 temperature sensor
|
||||
* with integrated fan control
|
||||
* Copyright (C) 2004-2005 Jean Delvare <khali@linux-fr.org>
|
||||
* Based on the lm90 driver.
|
||||
*
|
||||
* The LM63 is a sensor chip made by National Semiconductor. It measures
|
||||
* two temperatures (its own and one external one) and the speed of one
|
||||
* fan, those speed it can additionally control. Complete datasheet can be
|
||||
* obtained from National's website at:
|
||||
* http://www.national.com/pf/LM/LM63.html
|
||||
*
|
||||
* The LM63 is basically an LM86 with fan speed monitoring and control
|
||||
* capabilities added. It misses some of the LM86 features though:
|
||||
* - No low limit for local temperature.
|
||||
* - No critical limit for local temperature.
|
||||
* - Critical limit for remote temperature can be changed only once. We
|
||||
* will consider that the critical limit is read-only.
|
||||
*
|
||||
* The datasheet isn't very clear about what the tachometer reading is.
|
||||
* I had a explanation from National Semiconductor though. The two lower
|
||||
* bits of the read value have to be masked out. The value is still 16 bit
|
||||
* in width.
|
||||
*
|
||||
* 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/i2c-sensor.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
|
||||
/*
|
||||
* Addresses to scan
|
||||
* Address is fully defined internally and cannot be changed.
|
||||
*/
|
||||
|
||||
static unsigned short normal_i2c[] = { 0x4c, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/*
|
||||
* Insmod parameters
|
||||
*/
|
||||
|
||||
SENSORS_INSMOD_1(lm63);
|
||||
|
||||
/*
|
||||
* The LM63 registers
|
||||
*/
|
||||
|
||||
#define LM63_REG_CONFIG1 0x03
|
||||
#define LM63_REG_CONFIG2 0xBF
|
||||
#define LM63_REG_CONFIG_FAN 0x4A
|
||||
|
||||
#define LM63_REG_TACH_COUNT_MSB 0x47
|
||||
#define LM63_REG_TACH_COUNT_LSB 0x46
|
||||
#define LM63_REG_TACH_LIMIT_MSB 0x49
|
||||
#define LM63_REG_TACH_LIMIT_LSB 0x48
|
||||
|
||||
#define LM63_REG_PWM_VALUE 0x4C
|
||||
#define LM63_REG_PWM_FREQ 0x4D
|
||||
|
||||
#define LM63_REG_LOCAL_TEMP 0x00
|
||||
#define LM63_REG_LOCAL_HIGH 0x05
|
||||
|
||||
#define LM63_REG_REMOTE_TEMP_MSB 0x01
|
||||
#define LM63_REG_REMOTE_TEMP_LSB 0x10
|
||||
#define LM63_REG_REMOTE_OFFSET_MSB 0x11
|
||||
#define LM63_REG_REMOTE_OFFSET_LSB 0x12
|
||||
#define LM63_REG_REMOTE_HIGH_MSB 0x07
|
||||
#define LM63_REG_REMOTE_HIGH_LSB 0x13
|
||||
#define LM63_REG_REMOTE_LOW_MSB 0x08
|
||||
#define LM63_REG_REMOTE_LOW_LSB 0x14
|
||||
#define LM63_REG_REMOTE_TCRIT 0x19
|
||||
#define LM63_REG_REMOTE_TCRIT_HYST 0x21
|
||||
|
||||
#define LM63_REG_ALERT_STATUS 0x02
|
||||
#define LM63_REG_ALERT_MASK 0x16
|
||||
|
||||
#define LM63_REG_MAN_ID 0xFE
|
||||
#define LM63_REG_CHIP_ID 0xFF
|
||||
|
||||
/*
|
||||
* Conversions and various macros
|
||||
* For tachometer counts, the LM63 uses 16-bit values.
|
||||
* For local temperature and high limit, remote critical limit and hysteresis
|
||||
* value, it uses signed 8-bit values with LSB = 1 degree Celsius.
|
||||
* For remote temperature, low and high limits, it uses signed 11-bit values
|
||||
* with LSB = 0.125 degree Celsius, left-justified in 16-bit registers.
|
||||
*/
|
||||
|
||||
#define FAN_FROM_REG(reg) ((reg) == 0xFFFC || (reg) == 0 ? 0 : \
|
||||
5400000 / (reg))
|
||||
#define FAN_TO_REG(val) ((val) <= 82 ? 0xFFFC : \
|
||||
(5400000 / (val)) & 0xFFFC)
|
||||
#define TEMP8_FROM_REG(reg) ((reg) * 1000)
|
||||
#define TEMP8_TO_REG(val) ((val) <= -128000 ? -128 : \
|
||||
(val) >= 127000 ? 127 : \
|
||||
(val) < 0 ? ((val) - 500) / 1000 : \
|
||||
((val) + 500) / 1000)
|
||||
#define TEMP11_FROM_REG(reg) ((reg) / 32 * 125)
|
||||
#define TEMP11_TO_REG(val) ((val) <= -128000 ? 0x8000 : \
|
||||
(val) >= 127875 ? 0x7FE0 : \
|
||||
(val) < 0 ? ((val) - 62) / 125 * 32 : \
|
||||
((val) + 62) / 125 * 32)
|
||||
#define HYST_TO_REG(val) ((val) <= 0 ? 0 : \
|
||||
(val) >= 127000 ? 127 : \
|
||||
((val) + 500) / 1000)
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
*/
|
||||
|
||||
static int lm63_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int lm63_detach_client(struct i2c_client *client);
|
||||
|
||||
static struct lm63_data *lm63_update_device(struct device *dev);
|
||||
|
||||
static int lm63_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void lm63_init_client(struct i2c_client *client);
|
||||
|
||||
/*
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
|
||||
static struct i2c_driver lm63_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm63",
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = lm63_attach_adapter,
|
||||
.detach_client = lm63_detach_client,
|
||||
};
|
||||
|
||||
/*
|
||||
* Client data (each client gets its own)
|
||||
*/
|
||||
|
||||
struct lm63_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
|
||||
/* registers values */
|
||||
u8 config, config_fan;
|
||||
u16 fan[2]; /* 0: input
|
||||
1: low limit */
|
||||
u8 pwm1_freq;
|
||||
u8 pwm1_value;
|
||||
s8 temp8[3]; /* 0: local input
|
||||
1: local high limit
|
||||
2: remote critical limit */
|
||||
s16 temp11[3]; /* 0: remote input
|
||||
1: remote low limit
|
||||
2: remote high limit */
|
||||
u8 temp2_crit_hyst;
|
||||
u8 alarms;
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs callback functions and files
|
||||
*/
|
||||
|
||||
static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct lm63_data *data = lm63_update_device(dev);
|
||||
return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t set_fan(struct device *dev, struct device_attribute *dummy,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm63_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->fan[1] = FAN_TO_REG(val);
|
||||
i2c_smbus_write_byte_data(client, LM63_REG_TACH_LIMIT_LSB,
|
||||
data->fan[1] & 0xFF);
|
||||
i2c_smbus_write_byte_data(client, LM63_REG_TACH_LIMIT_MSB,
|
||||
data->fan[1] >> 8);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_pwm1(struct device *dev, struct device_attribute *dummy,
|
||||
char *buf)
|
||||
{
|
||||
struct lm63_data *data = lm63_update_device(dev);
|
||||
return sprintf(buf, "%d\n", data->pwm1_value >= 2 * data->pwm1_freq ?
|
||||
255 : (data->pwm1_value * 255 + data->pwm1_freq) /
|
||||
(2 * data->pwm1_freq));
|
||||
}
|
||||
|
||||
static ssize_t set_pwm1(struct device *dev, struct device_attribute *dummy,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm63_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val;
|
||||
|
||||
if (!(data->config_fan & 0x20)) /* register is read-only */
|
||||
return -EPERM;
|
||||
|
||||
val = simple_strtoul(buf, NULL, 10);
|
||||
down(&data->update_lock);
|
||||
data->pwm1_value = val <= 0 ? 0 :
|
||||
val >= 255 ? 2 * data->pwm1_freq :
|
||||
(val * data->pwm1_freq * 2 + 127) / 255;
|
||||
i2c_smbus_write_byte_data(client, LM63_REG_PWM_VALUE, data->pwm1_value);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_pwm1_enable(struct device *dev, struct device_attribute *dummy,
|
||||
char *buf)
|
||||
{
|
||||
struct lm63_data *data = lm63_update_device(dev);
|
||||
return sprintf(buf, "%d\n", data->config_fan & 0x20 ? 1 : 2);
|
||||
}
|
||||
|
||||
static ssize_t show_temp8(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct lm63_data *data = lm63_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP8_FROM_REG(data->temp8[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t set_temp8(struct device *dev, struct device_attribute *dummy,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm63_data *data = i2c_get_clientdata(client);
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp8[1] = TEMP8_TO_REG(val);
|
||||
i2c_smbus_write_byte_data(client, LM63_REG_LOCAL_HIGH, data->temp8[1]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_temp11(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct lm63_data *data = lm63_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP11_FROM_REG(data->temp11[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t set_temp11(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
static const u8 reg[4] = {
|
||||
LM63_REG_REMOTE_LOW_MSB,
|
||||
LM63_REG_REMOTE_LOW_LSB,
|
||||
LM63_REG_REMOTE_HIGH_MSB,
|
||||
LM63_REG_REMOTE_HIGH_LSB,
|
||||
};
|
||||
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm63_data *data = i2c_get_clientdata(client);
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
int nr = attr->index;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp11[nr] = TEMP11_TO_REG(val);
|
||||
i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2],
|
||||
data->temp11[nr] >> 8);
|
||||
i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2 + 1],
|
||||
data->temp11[nr] & 0xff);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Hysteresis register holds a relative value, while we want to present
|
||||
an absolute to user-space */
|
||||
static ssize_t show_temp2_crit_hyst(struct device *dev, struct device_attribute *dummy,
|
||||
char *buf)
|
||||
{
|
||||
struct lm63_data *data = lm63_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP8_FROM_REG(data->temp8[2])
|
||||
- TEMP8_FROM_REG(data->temp2_crit_hyst));
|
||||
}
|
||||
|
||||
/* And now the other way around, user-space provides an absolute
|
||||
hysteresis value and we have to store a relative one */
|
||||
static ssize_t set_temp2_crit_hyst(struct device *dev, struct device_attribute *dummy,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm63_data *data = i2c_get_clientdata(client);
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
long hyst;
|
||||
|
||||
down(&data->update_lock);
|
||||
hyst = TEMP8_FROM_REG(data->temp8[2]) - val;
|
||||
i2c_smbus_write_byte_data(client, LM63_REG_REMOTE_TCRIT_HYST,
|
||||
HYST_TO_REG(hyst));
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy,
|
||||
char *buf)
|
||||
{
|
||||
struct lm63_data *data = lm63_update_device(dev);
|
||||
return sprintf(buf, "%u\n", data->alarms);
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan,
|
||||
set_fan, 1);
|
||||
|
||||
static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1);
|
||||
static DEVICE_ATTR(pwm1_enable, S_IRUGO, show_pwm1_enable, NULL);
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp8, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp8,
|
||||
set_temp8, 1);
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp11, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp11,
|
||||
set_temp11, 1);
|
||||
static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp11,
|
||||
set_temp11, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp8, NULL, 2);
|
||||
static DEVICE_ATTR(temp2_crit_hyst, S_IWUSR | S_IRUGO, show_temp2_crit_hyst,
|
||||
set_temp2_crit_hyst);
|
||||
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static int lm63_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, lm63_detect);
|
||||
}
|
||||
|
||||
/*
|
||||
* The following function does more than just detection. If detection
|
||||
* succeeds, it also registers the new chip.
|
||||
*/
|
||||
static int lm63_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct lm63_data *data;
|
||||
int err = 0;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct lm63_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct lm63_data));
|
||||
|
||||
/* The common I2C client data is placed right before the
|
||||
LM63-specific data. */
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &lm63_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Default to an LM63 if forced */
|
||||
if (kind == 0)
|
||||
kind = lm63;
|
||||
|
||||
if (kind < 0) { /* must identify */
|
||||
u8 man_id, chip_id, reg_config1, reg_config2;
|
||||
u8 reg_alert_status, reg_alert_mask;
|
||||
|
||||
man_id = i2c_smbus_read_byte_data(new_client,
|
||||
LM63_REG_MAN_ID);
|
||||
chip_id = i2c_smbus_read_byte_data(new_client,
|
||||
LM63_REG_CHIP_ID);
|
||||
reg_config1 = i2c_smbus_read_byte_data(new_client,
|
||||
LM63_REG_CONFIG1);
|
||||
reg_config2 = i2c_smbus_read_byte_data(new_client,
|
||||
LM63_REG_CONFIG2);
|
||||
reg_alert_status = i2c_smbus_read_byte_data(new_client,
|
||||
LM63_REG_ALERT_STATUS);
|
||||
reg_alert_mask = i2c_smbus_read_byte_data(new_client,
|
||||
LM63_REG_ALERT_MASK);
|
||||
|
||||
if (man_id == 0x01 /* National Semiconductor */
|
||||
&& chip_id == 0x41 /* LM63 */
|
||||
&& (reg_config1 & 0x18) == 0x00
|
||||
&& (reg_config2 & 0xF8) == 0x00
|
||||
&& (reg_alert_status & 0x20) == 0x00
|
||||
&& (reg_alert_mask & 0xA4) == 0xA4) {
|
||||
kind = lm63;
|
||||
} else { /* failed */
|
||||
dev_dbg(&adapter->dev, "Unsupported chip "
|
||||
"(man_id=0x%02X, chip_id=0x%02X).\n",
|
||||
man_id, chip_id);
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
|
||||
strlcpy(new_client->name, "lm63", I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Initialize the LM63 chip */
|
||||
lm63_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
if (data->config & 0x04) { /* tachometer enabled */
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_fan1_input.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_fan1_min.dev_attr);
|
||||
}
|
||||
device_create_file(&new_client->dev, &dev_attr_pwm1);
|
||||
device_create_file(&new_client->dev, &dev_attr_pwm1_enable);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp1_input.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp2_input.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp2_min.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp1_max.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp2_max.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp2_crit.dev_attr);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp2_crit_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Idealy we shouldn't have to initialize anything, since the BIOS
|
||||
should have taken care of everything */
|
||||
static void lm63_init_client(struct i2c_client *client)
|
||||
{
|
||||
struct lm63_data *data = i2c_get_clientdata(client);
|
||||
|
||||
data->config = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1);
|
||||
data->config_fan = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_CONFIG_FAN);
|
||||
|
||||
/* Start converting if needed */
|
||||
if (data->config & 0x40) { /* standby */
|
||||
dev_dbg(&client->dev, "Switching to operational mode");
|
||||
data->config &= 0xA7;
|
||||
i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1,
|
||||
data->config);
|
||||
}
|
||||
|
||||
/* We may need pwm1_freq before ever updating the client data */
|
||||
data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ);
|
||||
if (data->pwm1_freq == 0)
|
||||
data->pwm1_freq = 1;
|
||||
|
||||
/* Show some debug info about the LM63 configuration */
|
||||
dev_dbg(&client->dev, "Alert/tach pin configured for %s\n",
|
||||
(data->config & 0x04) ? "tachometer input" :
|
||||
"alert output");
|
||||
dev_dbg(&client->dev, "PWM clock %s kHz, output frequency %u Hz\n",
|
||||
(data->config_fan & 0x08) ? "1.4" : "360",
|
||||
((data->config_fan & 0x08) ? 700 : 180000) / data->pwm1_freq);
|
||||
dev_dbg(&client->dev, "PWM output active %s, %s mode\n",
|
||||
(data->config_fan & 0x10) ? "low" : "high",
|
||||
(data->config_fan & 0x20) ? "manual" : "auto");
|
||||
}
|
||||
|
||||
static int lm63_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, "
|
||||
"client not detached\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct lm63_data *lm63_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm63_data *data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
||||
if (data->config & 0x04) { /* tachometer enabled */
|
||||
/* order matters for fan1_input */
|
||||
data->fan[0] = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_TACH_COUNT_LSB) & 0xFC;
|
||||
data->fan[0] |= i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_TACH_COUNT_MSB) << 8;
|
||||
data->fan[1] = (i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_TACH_LIMIT_LSB) & 0xFC)
|
||||
| (i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_TACH_LIMIT_MSB) << 8);
|
||||
}
|
||||
|
||||
data->pwm1_freq = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_PWM_FREQ);
|
||||
if (data->pwm1_freq == 0)
|
||||
data->pwm1_freq = 1;
|
||||
data->pwm1_value = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_PWM_VALUE);
|
||||
|
||||
data->temp8[0] = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_LOCAL_TEMP);
|
||||
data->temp8[1] = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_LOCAL_HIGH);
|
||||
|
||||
/* order matters for temp2_input */
|
||||
data->temp11[0] = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_REMOTE_TEMP_MSB) << 8;
|
||||
data->temp11[0] |= i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_REMOTE_TEMP_LSB);
|
||||
data->temp11[1] = (i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_REMOTE_LOW_MSB) << 8)
|
||||
| i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_REMOTE_LOW_LSB);
|
||||
data->temp11[2] = (i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_REMOTE_HIGH_MSB) << 8)
|
||||
| i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_REMOTE_HIGH_LSB);
|
||||
data->temp8[2] = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_REMOTE_TCRIT);
|
||||
data->temp2_crit_hyst = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_REMOTE_TCRIT_HYST);
|
||||
|
||||
data->alarms = i2c_smbus_read_byte_data(client,
|
||||
LM63_REG_ALERT_STATUS) & 0x7F;
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_lm63_init(void)
|
||||
{
|
||||
return i2c_add_driver(&lm63_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_lm63_exit(void)
|
||||
{
|
||||
i2c_del_driver(&lm63_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
|
||||
MODULE_DESCRIPTION("LM63 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_lm63_init);
|
||||
module_exit(sensors_lm63_exit);
|
||||
296
drivers/hwmon/lm75.c
Normal file
296
drivers/hwmon/lm75.c
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
/*
|
||||
lm75.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Copyright (c) 1998, 1999 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; 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/i2c-sensor.h>
|
||||
#include "lm75.h"
|
||||
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
|
||||
0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_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
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct lm75_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore 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;
|
||||
};
|
||||
|
||||
static int lm75_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int lm75_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void lm75_init_client(struct i2c_client *client);
|
||||
static int lm75_detach_client(struct i2c_client *client);
|
||||
static int lm75_read_value(struct i2c_client *client, u8 reg);
|
||||
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value);
|
||||
static struct lm75_data *lm75_update_device(struct device *dev);
|
||||
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver lm75_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm75",
|
||||
.id = I2C_DRIVERID_LM75,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = lm75_attach_adapter,
|
||||
.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)); \
|
||||
}
|
||||
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); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->value = LM75_TEMP_TO_REG(temp); \
|
||||
lm75_write_value(client, reg, data->value); \
|
||||
up(&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 int lm75_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, lm75_detect);
|
||||
}
|
||||
|
||||
/* This function is called by i2c_detect */
|
||||
static int lm75_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
int i;
|
||||
struct i2c_client *new_client;
|
||||
struct lm75_data *data;
|
||||
int err = 0;
|
||||
const char *name = "";
|
||||
|
||||
/* Make sure we aren't probing the ISA bus!! This is just a safety check
|
||||
at this moment; i2c_detect really won't call us. */
|
||||
#ifdef DEBUG
|
||||
if (i2c_is_isa_adapter(adapter)) {
|
||||
dev_dbg(&adapter->dev,
|
||||
"lm75_detect called for an ISA bus adapter?!?\n");
|
||||
goto exit;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
goto exit;
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
client structure, even though we cannot fill it completely yet.
|
||||
But it allows us to access lm75_{read,write}_value. */
|
||||
if (!(data = kmalloc(sizeof(struct lm75_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct lm75_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &lm75_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Now, we do the remaining detection. There is no identification-
|
||||
dedicated register so we have to rely on several tricks:
|
||||
unused bits, registers cycling over 8-address boundaries,
|
||||
addresses 0x04-0x07 returning the last read value.
|
||||
The cycling+unused addresses combination is not tested,
|
||||
since it would significantly slow the detection down and would
|
||||
hardly add any value. */
|
||||
if (kind < 0) {
|
||||
int cur, conf, hyst, os;
|
||||
|
||||
/* Unused addresses */
|
||||
cur = i2c_smbus_read_word_data(new_client, 0);
|
||||
conf = i2c_smbus_read_byte_data(new_client, 1);
|
||||
hyst = i2c_smbus_read_word_data(new_client, 2);
|
||||
if (i2c_smbus_read_word_data(new_client, 4) != hyst
|
||||
|| i2c_smbus_read_word_data(new_client, 5) != hyst
|
||||
|| i2c_smbus_read_word_data(new_client, 6) != hyst
|
||||
|| i2c_smbus_read_word_data(new_client, 7) != hyst)
|
||||
goto exit_free;
|
||||
os = i2c_smbus_read_word_data(new_client, 3);
|
||||
if (i2c_smbus_read_word_data(new_client, 4) != os
|
||||
|| i2c_smbus_read_word_data(new_client, 5) != os
|
||||
|| i2c_smbus_read_word_data(new_client, 6) != os
|
||||
|| i2c_smbus_read_word_data(new_client, 7) != os)
|
||||
goto exit_free;
|
||||
|
||||
/* Unused bits */
|
||||
if (conf & 0xe0)
|
||||
goto exit_free;
|
||||
|
||||
/* Addresses cycling */
|
||||
for (i = 8; i < 0xff; i += 8)
|
||||
if (i2c_smbus_read_byte_data(new_client, i + 1) != conf
|
||||
|| i2c_smbus_read_word_data(new_client, i + 2) != hyst
|
||||
|| i2c_smbus_read_word_data(new_client, i + 3) != os)
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Determine the chip type - only one kind supported! */
|
||||
if (kind <= 0)
|
||||
kind = lm75;
|
||||
|
||||
if (kind == lm75) {
|
||||
name = "lm75";
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields and put it into the global list */
|
||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Initialize the LM75 chip */
|
||||
lm75_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lm75_detach_client(struct i2c_client *client)
|
||||
{
|
||||
i2c_detach_client(client);
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* All registers are word-sized, except for the configuration register.
|
||||
LM75 uses a high-byte first convention, which is exactly opposite to
|
||||
the usual practice. */
|
||||
static int lm75_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
if (reg == LM75_REG_CONF)
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
else
|
||||
return swab16(i2c_smbus_read_word_data(client, reg));
|
||||
}
|
||||
|
||||
/* All registers are word-sized, except for the configuration register.
|
||||
LM75 uses a high-byte first convention, which is exactly opposite to
|
||||
the usual practice. */
|
||||
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value)
|
||||
{
|
||||
if (reg == LM75_REG_CONF)
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
else
|
||||
return i2c_smbus_write_word_data(client, reg, swab16(value));
|
||||
}
|
||||
|
||||
static void lm75_init_client(struct i2c_client *client)
|
||||
{
|
||||
/* Initialize the LM75 chip */
|
||||
lm75_write_value(client, LM75_REG_CONF, 0);
|
||||
}
|
||||
|
||||
static struct lm75_data *lm75_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm75_data *data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
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);
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_lm75_init(void)
|
||||
{
|
||||
return i2c_add_driver(&lm75_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_lm75_exit(void)
|
||||
{
|
||||
i2c_del_driver(&lm75_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
|
||||
MODULE_DESCRIPTION("LM75 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_lm75_init);
|
||||
module_exit(sensors_lm75_exit);
|
||||
49
drivers/hwmon/lm75.h
Normal file
49
drivers/hwmon/lm75.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
lm75.h - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
This file contains common code for encoding/decoding LM75 type
|
||||
temperature readings, which are emulated by many of the chips
|
||||
we support. As the user is unlikely to load more than one driver
|
||||
which contains this code, we don't worry about the wasted space.
|
||||
*/
|
||||
|
||||
#include <linux/i2c-sensor.h>
|
||||
|
||||
/* straight from the datasheet */
|
||||
#define LM75_TEMP_MIN (-55000)
|
||||
#define LM75_TEMP_MAX 125000
|
||||
|
||||
/* TEMP: 0.001C/bit (-55C to +125C)
|
||||
REG: (0.5C/bit, two's complement) << 7 */
|
||||
static inline u16 LM75_TEMP_TO_REG(int temp)
|
||||
{
|
||||
int ntemp = SENSORS_LIMIT(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
||||
ntemp += (ntemp<0 ? -250 : 250);
|
||||
return (u16)((ntemp / 500) << 7);
|
||||
}
|
||||
|
||||
static inline int LM75_TEMP_FROM_REG(u16 reg)
|
||||
{
|
||||
/* use integer division instead of equivalent right shift to
|
||||
guarantee arithmetic shift and preserve the sign */
|
||||
return ((s16)reg / 128) * 500;
|
||||
}
|
||||
|
||||
420
drivers/hwmon/lm77.c
Normal file
420
drivers/hwmon/lm77.c
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
lm77.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
|
||||
Copyright (c) 2004 Andras BALI <drewie@freemail.hu>
|
||||
|
||||
Heavily based on lm75.c by Frodo Looijaard <frodol@dds.nl>. The LM77
|
||||
is a temperature sensor and thermal window comparator with 0.5 deg
|
||||
resolution made by National Semiconductor. Complete datasheet can be
|
||||
obtained at their site:
|
||||
http://www.national.com/pf/LM/LM77.html
|
||||
|
||||
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/i2c-sensor.h>
|
||||
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_1(lm77);
|
||||
|
||||
/* The LM77 registers */
|
||||
#define LM77_REG_TEMP 0x00
|
||||
#define LM77_REG_CONF 0x01
|
||||
#define LM77_REG_TEMP_HYST 0x02
|
||||
#define LM77_REG_TEMP_CRIT 0x03
|
||||
#define LM77_REG_TEMP_MIN 0x04
|
||||
#define LM77_REG_TEMP_MAX 0x05
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct lm77_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid;
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
int temp_input; /* Temperatures */
|
||||
int temp_crit;
|
||||
int temp_min;
|
||||
int temp_max;
|
||||
int temp_hyst;
|
||||
u8 alarms;
|
||||
};
|
||||
|
||||
static int lm77_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int lm77_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void lm77_init_client(struct i2c_client *client);
|
||||
static int lm77_detach_client(struct i2c_client *client);
|
||||
static u16 lm77_read_value(struct i2c_client *client, u8 reg);
|
||||
static int lm77_write_value(struct i2c_client *client, u8 reg, u16 value);
|
||||
|
||||
static struct lm77_data *lm77_update_device(struct device *dev);
|
||||
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver lm77_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm77",
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = lm77_attach_adapter,
|
||||
.detach_client = lm77_detach_client,
|
||||
};
|
||||
|
||||
/* straight from the datasheet */
|
||||
#define LM77_TEMP_MIN (-55000)
|
||||
#define LM77_TEMP_MAX 125000
|
||||
|
||||
/* In the temperature registers, the low 3 bits are not part of the
|
||||
temperature values; they are the status bits. */
|
||||
static inline u16 LM77_TEMP_TO_REG(int temp)
|
||||
{
|
||||
int ntemp = SENSORS_LIMIT(temp, LM77_TEMP_MIN, LM77_TEMP_MAX);
|
||||
return (u16)((ntemp / 500) * 8);
|
||||
}
|
||||
|
||||
static inline int LM77_TEMP_FROM_REG(u16 reg)
|
||||
{
|
||||
return ((int)reg / 8) * 500;
|
||||
}
|
||||
|
||||
/* sysfs stuff */
|
||||
|
||||
/* read routines for temperature limits */
|
||||
#define show(value) \
|
||||
static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct lm77_data *data = lm77_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", data->value); \
|
||||
}
|
||||
|
||||
show(temp_input);
|
||||
show(temp_crit);
|
||||
show(temp_min);
|
||||
show(temp_max);
|
||||
show(alarms);
|
||||
|
||||
/* read routines for hysteresis values */
|
||||
static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct lm77_data *data = lm77_update_device(dev);
|
||||
return sprintf(buf, "%d\n", data->temp_crit - data->temp_hyst);
|
||||
}
|
||||
static ssize_t show_temp_min_hyst(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct lm77_data *data = lm77_update_device(dev);
|
||||
return sprintf(buf, "%d\n", data->temp_min + data->temp_hyst);
|
||||
}
|
||||
static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct lm77_data *data = lm77_update_device(dev);
|
||||
return sprintf(buf, "%d\n", data->temp_max - data->temp_hyst);
|
||||
}
|
||||
|
||||
/* write routines */
|
||||
#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 lm77_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtoul(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->value = val; \
|
||||
lm77_write_value(client, reg, LM77_TEMP_TO_REG(data->value)); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
}
|
||||
|
||||
set(temp_min, LM77_REG_TEMP_MIN);
|
||||
set(temp_max, LM77_REG_TEMP_MAX);
|
||||
|
||||
/* hysteresis is stored as a relative value on the chip, so it has to be
|
||||
converted first */
|
||||
static ssize_t set_temp_crit_hyst(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm77_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp_hyst = data->temp_crit - val;
|
||||
lm77_write_value(client, LM77_REG_TEMP_HYST,
|
||||
LM77_TEMP_TO_REG(data->temp_hyst));
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* preserve hysteresis when setting T_crit */
|
||||
static ssize_t set_temp_crit(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm77_data *data = i2c_get_clientdata(client);
|
||||
long val = simple_strtoul(buf, NULL, 10);
|
||||
int oldcrithyst;
|
||||
|
||||
down(&data->update_lock);
|
||||
oldcrithyst = data->temp_crit - data->temp_hyst;
|
||||
data->temp_crit = val;
|
||||
data->temp_hyst = data->temp_crit - oldcrithyst;
|
||||
lm77_write_value(client, LM77_REG_TEMP_CRIT,
|
||||
LM77_TEMP_TO_REG(data->temp_crit));
|
||||
lm77_write_value(client, LM77_REG_TEMP_HYST,
|
||||
LM77_TEMP_TO_REG(data->temp_hyst));
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO,
|
||||
show_temp_input, NULL);
|
||||
static DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO,
|
||||
show_temp_crit, set_temp_crit);
|
||||
static DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO,
|
||||
show_temp_min, set_temp_min);
|
||||
static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
||||
show_temp_max, set_temp_max);
|
||||
|
||||
static DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO,
|
||||
show_temp_crit_hyst, set_temp_crit_hyst);
|
||||
static DEVICE_ATTR(temp1_min_hyst, S_IRUGO,
|
||||
show_temp_min_hyst, NULL);
|
||||
static DEVICE_ATTR(temp1_max_hyst, S_IRUGO,
|
||||
show_temp_max_hyst, NULL);
|
||||
|
||||
static DEVICE_ATTR(alarms, S_IRUGO,
|
||||
show_alarms, NULL);
|
||||
|
||||
static int lm77_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, lm77_detect);
|
||||
}
|
||||
|
||||
/* This function is called by i2c_detect */
|
||||
static int lm77_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct lm77_data *data;
|
||||
int err = 0;
|
||||
const char *name = "";
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
goto exit;
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
client structure, even though we cannot fill it completely yet.
|
||||
But it allows us to access lm77_{read,write}_value. */
|
||||
if (!(data = kmalloc(sizeof(struct lm77_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct lm77_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &lm77_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Here comes the remaining detection. Since the LM77 has no
|
||||
register dedicated to identification, we have to rely on the
|
||||
following tricks:
|
||||
|
||||
1. the high 4 bits represent the sign and thus they should
|
||||
always be the same
|
||||
2. the high 3 bits are unused in the configuration register
|
||||
3. addresses 0x06 and 0x07 return the last read value
|
||||
4. registers cycling over 8-address boundaries
|
||||
|
||||
Word-sized registers are high-byte first. */
|
||||
if (kind < 0) {
|
||||
int i, cur, conf, hyst, crit, min, max;
|
||||
|
||||
/* addresses cycling */
|
||||
cur = i2c_smbus_read_word_data(new_client, 0);
|
||||
conf = i2c_smbus_read_byte_data(new_client, 1);
|
||||
hyst = i2c_smbus_read_word_data(new_client, 2);
|
||||
crit = i2c_smbus_read_word_data(new_client, 3);
|
||||
min = i2c_smbus_read_word_data(new_client, 4);
|
||||
max = i2c_smbus_read_word_data(new_client, 5);
|
||||
for (i = 8; i <= 0xff; i += 8)
|
||||
if (i2c_smbus_read_byte_data(new_client, i + 1) != conf
|
||||
|| i2c_smbus_read_word_data(new_client, i + 2) != hyst
|
||||
|| i2c_smbus_read_word_data(new_client, i + 3) != crit
|
||||
|| i2c_smbus_read_word_data(new_client, i + 4) != min
|
||||
|| i2c_smbus_read_word_data(new_client, i + 5) != max)
|
||||
goto exit_free;
|
||||
|
||||
/* sign bits */
|
||||
if (((cur & 0x00f0) != 0xf0 && (cur & 0x00f0) != 0x0)
|
||||
|| ((hyst & 0x00f0) != 0xf0 && (hyst & 0x00f0) != 0x0)
|
||||
|| ((crit & 0x00f0) != 0xf0 && (crit & 0x00f0) != 0x0)
|
||||
|| ((min & 0x00f0) != 0xf0 && (min & 0x00f0) != 0x0)
|
||||
|| ((max & 0x00f0) != 0xf0 && (max & 0x00f0) != 0x0))
|
||||
goto exit_free;
|
||||
|
||||
/* unused bits */
|
||||
if (conf & 0xe0)
|
||||
goto exit_free;
|
||||
|
||||
/* 0x06 and 0x07 return the last read value */
|
||||
cur = i2c_smbus_read_word_data(new_client, 0);
|
||||
if (i2c_smbus_read_word_data(new_client, 6) != cur
|
||||
|| i2c_smbus_read_word_data(new_client, 7) != cur)
|
||||
goto exit_free;
|
||||
hyst = i2c_smbus_read_word_data(new_client, 2);
|
||||
if (i2c_smbus_read_word_data(new_client, 6) != hyst
|
||||
|| i2c_smbus_read_word_data(new_client, 7) != hyst)
|
||||
goto exit_free;
|
||||
min = i2c_smbus_read_word_data(new_client, 4);
|
||||
if (i2c_smbus_read_word_data(new_client, 6) != min
|
||||
|| i2c_smbus_read_word_data(new_client, 7) != min)
|
||||
goto exit_free;
|
||||
|
||||
}
|
||||
|
||||
/* Determine the chip type - only one kind supported! */
|
||||
if (kind <= 0)
|
||||
kind = lm77;
|
||||
|
||||
if (kind == lm77) {
|
||||
name = "lm77";
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields and put it into the global list */
|
||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/* Initialize the LM77 chip */
|
||||
lm77_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_crit);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_crit_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_min_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lm77_detach_client(struct i2c_client *client)
|
||||
{
|
||||
i2c_detach_client(client);
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* All registers are word-sized, except for the configuration register.
|
||||
The LM77 uses the high-byte first convention. */
|
||||
static u16 lm77_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
if (reg == LM77_REG_CONF)
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
else
|
||||
return swab16(i2c_smbus_read_word_data(client, reg));
|
||||
}
|
||||
|
||||
static int lm77_write_value(struct i2c_client *client, u8 reg, u16 value)
|
||||
{
|
||||
if (reg == LM77_REG_CONF)
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
else
|
||||
return i2c_smbus_write_word_data(client, reg, swab16(value));
|
||||
}
|
||||
|
||||
static void lm77_init_client(struct i2c_client *client)
|
||||
{
|
||||
/* Initialize the LM77 chip - turn off shutdown mode */
|
||||
int conf = lm77_read_value(client, LM77_REG_CONF);
|
||||
if (conf & 1)
|
||||
lm77_write_value(client, LM77_REG_CONF, conf & 0xfe);
|
||||
}
|
||||
|
||||
static struct lm77_data *lm77_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm77_data *data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
dev_dbg(&client->dev, "Starting lm77 update\n");
|
||||
data->temp_input =
|
||||
LM77_TEMP_FROM_REG(lm77_read_value(client,
|
||||
LM77_REG_TEMP));
|
||||
data->temp_hyst =
|
||||
LM77_TEMP_FROM_REG(lm77_read_value(client,
|
||||
LM77_REG_TEMP_HYST));
|
||||
data->temp_crit =
|
||||
LM77_TEMP_FROM_REG(lm77_read_value(client,
|
||||
LM77_REG_TEMP_CRIT));
|
||||
data->temp_min =
|
||||
LM77_TEMP_FROM_REG(lm77_read_value(client,
|
||||
LM77_REG_TEMP_MIN));
|
||||
data->temp_max =
|
||||
LM77_TEMP_FROM_REG(lm77_read_value(client,
|
||||
LM77_REG_TEMP_MAX));
|
||||
data->alarms =
|
||||
lm77_read_value(client, LM77_REG_TEMP) & 0x0007;
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_lm77_init(void)
|
||||
{
|
||||
return i2c_add_driver(&lm77_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_lm77_exit(void)
|
||||
{
|
||||
i2c_del_driver(&lm77_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Andras BALI <drewie@freemail.hu>");
|
||||
MODULE_DESCRIPTION("LM77 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_lm77_init);
|
||||
module_exit(sensors_lm77_exit);
|
||||
795
drivers/hwmon/lm78.c
Normal file
795
drivers/hwmon/lm78.c
Normal file
|
|
@ -0,0 +1,795 @@
|
|||
/*
|
||||
lm78.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Copyright (c) 1998, 1999 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; 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/i2c-sensor.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x20, 0x21, 0x22, 0x23, 0x24,
|
||||
0x25, 0x26, 0x27, 0x28, 0x29,
|
||||
0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
|
||||
0x2f, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { 0x0290, I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_3(lm78, lm78j, lm79);
|
||||
|
||||
/* Many LM78 constants specified below */
|
||||
|
||||
/* Length of ISA address segment */
|
||||
#define LM78_EXTENT 8
|
||||
|
||||
/* Where are the ISA address/data registers relative to the base address */
|
||||
#define LM78_ADDR_REG_OFFSET 5
|
||||
#define LM78_DATA_REG_OFFSET 6
|
||||
|
||||
/* The LM78 registers */
|
||||
#define LM78_REG_IN_MAX(nr) (0x2b + (nr) * 2)
|
||||
#define LM78_REG_IN_MIN(nr) (0x2c + (nr) * 2)
|
||||
#define LM78_REG_IN(nr) (0x20 + (nr))
|
||||
|
||||
#define LM78_REG_FAN_MIN(nr) (0x3b + (nr))
|
||||
#define LM78_REG_FAN(nr) (0x28 + (nr))
|
||||
|
||||
#define LM78_REG_TEMP 0x27
|
||||
#define LM78_REG_TEMP_OVER 0x39
|
||||
#define LM78_REG_TEMP_HYST 0x3a
|
||||
|
||||
#define LM78_REG_ALARM1 0x41
|
||||
#define LM78_REG_ALARM2 0x42
|
||||
|
||||
#define LM78_REG_VID_FANDIV 0x47
|
||||
|
||||
#define LM78_REG_CONFIG 0x40
|
||||
#define LM78_REG_CHIPID 0x49
|
||||
#define LM78_REG_I2C_ADDR 0x48
|
||||
|
||||
|
||||
/* Conversions. Rounding and limit checking is only done on the TO_REG
|
||||
variants. */
|
||||
|
||||
/* IN: mV, (0V to 4.08V)
|
||||
REG: 16mV/bit */
|
||||
static inline u8 IN_TO_REG(unsigned long val)
|
||||
{
|
||||
unsigned long nval = SENSORS_LIMIT(val, 0, 4080);
|
||||
return (nval + 8) / 16;
|
||||
}
|
||||
#define IN_FROM_REG(val) ((val) * 16)
|
||||
|
||||
static inline u8 FAN_TO_REG(long rpm, int div)
|
||||
{
|
||||
if (rpm <= 0)
|
||||
return 255;
|
||||
return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, 254);
|
||||
}
|
||||
|
||||
static inline int FAN_FROM_REG(u8 val, int div)
|
||||
{
|
||||
return val==0 ? -1 : val==255 ? 0 : 1350000/(val*div);
|
||||
}
|
||||
|
||||
/* TEMP: mC (-128C to +127C)
|
||||
REG: 1C/bit, two's complement */
|
||||
static inline s8 TEMP_TO_REG(int val)
|
||||
{
|
||||
int nval = SENSORS_LIMIT(val, -128000, 127000) ;
|
||||
return nval<0 ? (nval-500)/1000 : (nval+500)/1000;
|
||||
}
|
||||
|
||||
static inline int TEMP_FROM_REG(s8 val)
|
||||
{
|
||||
return val * 1000;
|
||||
}
|
||||
|
||||
/* VID: mV
|
||||
REG: (see doc/vid) */
|
||||
static inline int VID_FROM_REG(u8 val)
|
||||
{
|
||||
return val==0x1f ? 0 : val>=0x10 ? 5100-val*100 : 2050-val*50;
|
||||
}
|
||||
|
||||
#define DIV_FROM_REG(val) (1 << (val))
|
||||
|
||||
/* There are some complications in a module like this. First off, LM78 chips
|
||||
may be both present on the SMBus and the ISA bus, and we have to handle
|
||||
those cases separately at some places. Second, there might be several
|
||||
LM78 chips available (well, actually, that is probably never done; but
|
||||
it is a clean illustration of how to handle a case like that). Finally,
|
||||
a specific chip may be attached to *both* ISA and SMBus, and we would
|
||||
not like to detect it double. Fortunately, in the case of the LM78 at
|
||||
least, a register tells us what SMBus address we are on, so that helps
|
||||
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 LM78, we need to keep some data in memory. That
|
||||
data is pointed to by lm78_list[NR]->data. The structure itself is
|
||||
dynamically allocated, at the same time when a new lm78 client is
|
||||
allocated. */
|
||||
struct lm78_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore lock;
|
||||
enum chips type;
|
||||
|
||||
struct semaphore update_lock;
|
||||
char valid; /* !=0 if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
||||
u8 in[7]; /* Register value */
|
||||
u8 in_max[7]; /* Register value */
|
||||
u8 in_min[7]; /* Register value */
|
||||
u8 fan[3]; /* Register value */
|
||||
u8 fan_min[3]; /* Register value */
|
||||
s8 temp; /* Register value */
|
||||
s8 temp_over; /* Register value */
|
||||
s8 temp_hyst; /* Register value */
|
||||
u8 fan_div[3]; /* Register encoding, shifted right */
|
||||
u8 vid; /* Register encoding, combined */
|
||||
u16 alarms; /* Register encoding, combined */
|
||||
};
|
||||
|
||||
|
||||
static int lm78_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 register);
|
||||
static int lm78_write_value(struct i2c_client *client, u8 register, u8 value);
|
||||
static struct lm78_data *lm78_update_device(struct device *dev);
|
||||
static void lm78_init_client(struct i2c_client *client);
|
||||
|
||||
|
||||
static struct i2c_driver lm78_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm78",
|
||||
.id = I2C_DRIVERID_LM78,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = lm78_attach_adapter,
|
||||
.detach_client = lm78_detach_client,
|
||||
};
|
||||
|
||||
/* 7 Voltages */
|
||||
static ssize_t show_in(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in[nr]));
|
||||
}
|
||||
|
||||
static ssize_t show_in_min(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[nr]));
|
||||
}
|
||||
|
||||
static ssize_t show_in_max(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[nr]));
|
||||
}
|
||||
|
||||
static ssize_t set_in_min(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->in_min[nr] = IN_TO_REG(val);
|
||||
lm78_write_value(client, LM78_REG_IN_MIN(nr), data->in_min[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t set_in_max(struct device *dev, const char *buf,
|
||||
size_t count, int nr)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->in_max[nr] = IN_TO_REG(val);
|
||||
lm78_write_value(client, LM78_REG_IN_MAX(nr), data->in_max[nr]);
|
||||
up(&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);
|
||||
|
||||
show_in_offset(0);
|
||||
show_in_offset(1);
|
||||
show_in_offset(2);
|
||||
show_in_offset(3);
|
||||
show_in_offset(4);
|
||||
show_in_offset(5);
|
||||
show_in_offset(6);
|
||||
|
||||
/* Temperature */
|
||||
static ssize_t show_temp(struct device *dev, struct device_attribute *attr, 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp_over = TEMP_TO_REG(val);
|
||||
lm78_write_value(client, LM78_REG_TEMP_OVER, data->temp_over);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_temp_hyst(struct device *dev, struct device_attribute *attr, 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)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp_hyst = TEMP_TO_REG(val);
|
||||
lm78_write_value(client, LM78_REG_TEMP_HYST, data->temp_hyst);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL);
|
||||
static DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR,
|
||||
show_temp_over, set_temp_over);
|
||||
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)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
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)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
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)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
down(&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]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_fan_div(struct device *dev, char *buf, int nr)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr]) );
|
||||
}
|
||||
|
||||
/* 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 suprise; 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)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
unsigned long min;
|
||||
u8 reg;
|
||||
|
||||
down(&data->update_lock);
|
||||
min = FAN_FROM_REG(data->fan_min[nr],
|
||||
DIV_FROM_REG(data->fan_div[nr]));
|
||||
|
||||
switch (val) {
|
||||
case 1: data->fan_div[nr] = 0; break;
|
||||
case 2: data->fan_div[nr] = 1; break;
|
||||
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 "
|
||||
"supported. Choose one of 1, 2, 4 or 8!\n", val);
|
||||
up(&data->update_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
reg = lm78_read_value(client, LM78_REG_VID_FANDIV);
|
||||
switch (nr) {
|
||||
case 0:
|
||||
reg = (reg & 0xcf) | (data->fan_div[nr] << 4);
|
||||
break;
|
||||
case 1:
|
||||
reg = (reg & 0x3f) | (data->fan_div[nr] << 6);
|
||||
break;
|
||||
}
|
||||
lm78_write_value(client, 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]);
|
||||
up(&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) ;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* VID */
|
||||
static ssize_t show_vid(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%d\n", VID_FROM_REG(data->vid));
|
||||
}
|
||||
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)
|
||||
{
|
||||
struct lm78_data *data = lm78_update_device(dev);
|
||||
return sprintf(buf, "%u\n", data->alarms);
|
||||
}
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
|
||||
/* This function is called when:
|
||||
* lm78_driver is inserted (when this module is loaded), for each
|
||||
available adapter
|
||||
* when a new adapter is inserted (and lm78_driver is still present) */
|
||||
static int lm78_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, lm78_detect);
|
||||
}
|
||||
|
||||
/* This function is called by i2c_detect */
|
||||
int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
int i, err;
|
||||
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)) {
|
||||
err = -ENODEV;
|
||||
goto ERROR0;
|
||||
}
|
||||
|
||||
/* Reserve the ISA region */
|
||||
if (is_isa)
|
||||
if (!request_region(address, LM78_EXTENT, lm78_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
client structure, even though we cannot fill it completely yet.
|
||||
But it allows us to access lm78_{read,write}_value. */
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct lm78_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto ERROR1;
|
||||
}
|
||||
memset(data, 0, sizeof(struct lm78_data));
|
||||
|
||||
new_client = &data->client;
|
||||
if (is_isa)
|
||||
init_MUTEX(&data->lock);
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &lm78_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Now, we do the remaining detection. */
|
||||
if (kind < 0) {
|
||||
if (lm78_read_value(new_client, LM78_REG_CONFIG) & 0x80) {
|
||||
err = -ENODEV;
|
||||
goto ERROR2;
|
||||
}
|
||||
if (!is_isa && (lm78_read_value(
|
||||
new_client, LM78_REG_I2C_ADDR) != address)) {
|
||||
err = -ENODEV;
|
||||
goto ERROR2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine the chip type. */
|
||||
if (kind <= 0) {
|
||||
i = lm78_read_value(new_client, LM78_REG_CHIPID);
|
||||
if (i == 0x00 || i == 0x20)
|
||||
kind = lm78;
|
||||
else if (i == 0x40)
|
||||
kind = lm78j;
|
||||
else if ((i & 0xfe) == 0xc0)
|
||||
kind = lm79;
|
||||
else {
|
||||
if (kind == 0)
|
||||
dev_warn(&adapter->dev, "Ignoring 'force' "
|
||||
"parameter for unknown chip at "
|
||||
"adapter %d, address 0x%02x\n",
|
||||
i2c_adapter_id(adapter), address);
|
||||
err = -ENODEV;
|
||||
goto ERROR2;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == lm78) {
|
||||
client_name = "lm78";
|
||||
} else if (kind == lm78j) {
|
||||
client_name = "lm78-j";
|
||||
} else if (kind == lm79) {
|
||||
client_name = "lm79";
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields and put into the global list */
|
||||
strlcpy(new_client->name, client_name, I2C_NAME_SIZE);
|
||||
data->type = kind;
|
||||
|
||||
data->valid = 0;
|
||||
init_MUTEX(&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));
|
||||
}
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in6_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in6_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in6_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan3_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan3_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
device_create_file(&new_client->dev, &dev_attr_cpu0_vid);
|
||||
|
||||
return 0;
|
||||
|
||||
ERROR2:
|
||||
kfree(data);
|
||||
ERROR1:
|
||||
if (is_isa)
|
||||
release_region(address, LM78_EXTENT);
|
||||
ERROR0:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lm78_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev,
|
||||
"Client deregistration failed, client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if(i2c_is_isa_client(client))
|
||||
release_region(client->addr, LM78_EXTENT);
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The SMBus locks itself, but ISA access must be locked explicitly!
|
||||
We don't want to lock the whole ISA bus, so we lock each client
|
||||
separately.
|
||||
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)
|
||||
{
|
||||
int res;
|
||||
if (i2c_is_isa_client(client)) {
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
down(&data->lock);
|
||||
outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET);
|
||||
res = inb_p(client->addr + LM78_DATA_REG_OFFSET);
|
||||
up(&data->lock);
|
||||
return res;
|
||||
} else
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
/* The SMBus locks itself, but ISA access muse be locked explicitly!
|
||||
We don't want to lock the whole ISA bus, so we lock each client
|
||||
separately.
|
||||
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.
|
||||
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)
|
||||
{
|
||||
if (i2c_is_isa_client(client)) {
|
||||
struct lm78_data *data = i2c_get_clientdata(client);
|
||||
down(&data->lock);
|
||||
outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET);
|
||||
outb_p(value, client->addr + LM78_DATA_REG_OFFSET);
|
||||
up(&data->lock);
|
||||
return 0;
|
||||
} else
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
/* Called when we have found a new LM78. It should set limits, etc. */
|
||||
static void lm78_init_client(struct i2c_client *client)
|
||||
{
|
||||
u8 config = lm78_read_value(client, LM78_REG_CONFIG);
|
||||
|
||||
/* Start monitoring */
|
||||
if (!(config & 0x01))
|
||||
lm78_write_value(client, LM78_REG_CONFIG,
|
||||
(config & 0xf7) | 0x01);
|
||||
}
|
||||
|
||||
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);
|
||||
int i;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
|
||||
dev_dbg(&client->dev, "Starting lm78 update\n");
|
||||
|
||||
for (i = 0; i <= 6; i++) {
|
||||
data->in[i] =
|
||||
lm78_read_value(client, LM78_REG_IN(i));
|
||||
data->in_min[i] =
|
||||
lm78_read_value(client, LM78_REG_IN_MIN(i));
|
||||
data->in_max[i] =
|
||||
lm78_read_value(client, LM78_REG_IN_MAX(i));
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
data->fan[i] =
|
||||
lm78_read_value(client, LM78_REG_FAN(i));
|
||||
data->fan_min[i] =
|
||||
lm78_read_value(client, LM78_REG_FAN_MIN(i));
|
||||
}
|
||||
data->temp = lm78_read_value(client, LM78_REG_TEMP);
|
||||
data->temp_over =
|
||||
lm78_read_value(client, LM78_REG_TEMP_OVER);
|
||||
data->temp_hyst =
|
||||
lm78_read_value(client, LM78_REG_TEMP_HYST);
|
||||
i = lm78_read_value(client, LM78_REG_VID_FANDIV);
|
||||
data->vid = i & 0x0f;
|
||||
if (data->type == lm79)
|
||||
data->vid |=
|
||||
(lm78_read_value(client, 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->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
|
||||
data->fan_div[2] = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sm_lm78_init(void)
|
||||
{
|
||||
return i2c_add_driver(&lm78_driver);
|
||||
}
|
||||
|
||||
static void __exit sm_lm78_exit(void)
|
||||
{
|
||||
i2c_del_driver(&lm78_driver);
|
||||
}
|
||||
|
||||
|
||||
|
||||
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
|
||||
MODULE_DESCRIPTION("LM78, LM78-J and LM79 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sm_lm78_init);
|
||||
module_exit(sm_lm78_exit);
|
||||
601
drivers/hwmon/lm80.c
Normal file
601
drivers/hwmon/lm80.c
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
/*
|
||||
* lm80.c - From lm_sensors, Linux kernel modules for hardware
|
||||
* monitoring
|
||||
* Copyright (C) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
|
||||
* and Philip Edelbrock <phil@netroedge.com>
|
||||
*
|
||||
* Ported to Linux 2.6 by Tiago Sousa <mirage@kaotik.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
|
||||
* 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/i2c-sensor.h>
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c,
|
||||
0x2d, 0x2e, 0x2f, I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
SENSORS_INSMOD_1(lm80);
|
||||
|
||||
/* Many LM80 constants specified below */
|
||||
|
||||
/* The LM80 registers */
|
||||
#define LM80_REG_IN_MAX(nr) (0x2a + (nr) * 2)
|
||||
#define LM80_REG_IN_MIN(nr) (0x2b + (nr) * 2)
|
||||
#define LM80_REG_IN(nr) (0x20 + (nr))
|
||||
|
||||
#define LM80_REG_FAN1 0x28
|
||||
#define LM80_REG_FAN2 0x29
|
||||
#define LM80_REG_FAN_MIN(nr) (0x3b + (nr))
|
||||
|
||||
#define LM80_REG_TEMP 0x27
|
||||
#define LM80_REG_TEMP_HOT_MAX 0x38
|
||||
#define LM80_REG_TEMP_HOT_HYST 0x39
|
||||
#define LM80_REG_TEMP_OS_MAX 0x3a
|
||||
#define LM80_REG_TEMP_OS_HYST 0x3b
|
||||
|
||||
#define LM80_REG_CONFIG 0x00
|
||||
#define LM80_REG_ALARM1 0x01
|
||||
#define LM80_REG_ALARM2 0x02
|
||||
#define LM80_REG_MASK1 0x03
|
||||
#define LM80_REG_MASK2 0x04
|
||||
#define LM80_REG_FANDIV 0x05
|
||||
#define LM80_REG_RES 0x06
|
||||
|
||||
|
||||
/* Conversions. Rounding and limit checking is only done on the TO_REG
|
||||
variants. Note that you should be a bit careful with which arguments
|
||||
these macros are called: arguments may be evaluated more than once.
|
||||
Fixing this is just not worth it. */
|
||||
|
||||
#define IN_TO_REG(val) (SENSORS_LIMIT(((val)+5)/10,0,255))
|
||||
#define IN_FROM_REG(val) ((val)*10)
|
||||
|
||||
static inline unsigned char FAN_TO_REG(unsigned rpm, unsigned div)
|
||||
{
|
||||
if (rpm == 0)
|
||||
return 255;
|
||||
rpm = SENSORS_LIMIT(rpm, 1, 1000000);
|
||||
return SENSORS_LIMIT((1350000 + rpm*div / 2) / (rpm*div), 1, 254);
|
||||
}
|
||||
|
||||
#define FAN_FROM_REG(val,div) ((val)==0?-1:\
|
||||
(val)==255?0:1350000/((div)*(val)))
|
||||
|
||||
static inline long TEMP_FROM_REG(u16 temp)
|
||||
{
|
||||
long res;
|
||||
|
||||
temp >>= 4;
|
||||
if (temp < 0x0800)
|
||||
res = 625 * (long) temp;
|
||||
else
|
||||
res = ((long) temp - 0x01000) * 625;
|
||||
|
||||
return res / 10;
|
||||
}
|
||||
|
||||
#define TEMP_LIMIT_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*1000)
|
||||
|
||||
#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT((val)<0?\
|
||||
((val)-500)/1000:((val)+500)/1000,0,255)
|
||||
|
||||
#define DIV_FROM_REG(val) (1 << (val))
|
||||
|
||||
/*
|
||||
* Client data (each client gets its own)
|
||||
*/
|
||||
|
||||
struct lm80_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid; /* !=0 if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
||||
u8 in[7]; /* Register value */
|
||||
u8 in_max[7]; /* Register value */
|
||||
u8 in_min[7]; /* Register value */
|
||||
u8 fan[2]; /* Register value */
|
||||
u8 fan_min[2]; /* Register value */
|
||||
u8 fan_div[2]; /* Register encoding, shifted right */
|
||||
u16 temp; /* Register values, shifted right */
|
||||
u8 temp_hot_max; /* Register value */
|
||||
u8 temp_hot_hyst; /* Register value */
|
||||
u8 temp_os_max; /* Register value */
|
||||
u8 temp_os_hyst; /* Register value */
|
||||
u16 alarms; /* Register encoding, combined */
|
||||
};
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
*/
|
||||
|
||||
static int lm80_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int lm80_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static void lm80_init_client(struct i2c_client *client);
|
||||
static int lm80_detach_client(struct i2c_client *client);
|
||||
static struct lm80_data *lm80_update_device(struct device *dev);
|
||||
static int lm80_read_value(struct i2c_client *client, u8 reg);
|
||||
static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value);
|
||||
|
||||
/*
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
|
||||
static struct i2c_driver lm80_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm80",
|
||||
.id = I2C_DRIVERID_LM80,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = lm80_attach_adapter,
|
||||
.detach_client = lm80_detach_client,
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
#define show_in(suffix, value) \
|
||||
static ssize_t show_in_##suffix(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct lm80_data *data = lm80_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", IN_FROM_REG(data->value)); \
|
||||
}
|
||||
show_in(min0, in_min[0]);
|
||||
show_in(min1, in_min[1]);
|
||||
show_in(min2, in_min[2]);
|
||||
show_in(min3, in_min[3]);
|
||||
show_in(min4, in_min[4]);
|
||||
show_in(min5, in_min[5]);
|
||||
show_in(min6, in_min[6]);
|
||||
show_in(max0, in_max[0]);
|
||||
show_in(max1, in_max[1]);
|
||||
show_in(max2, in_max[2]);
|
||||
show_in(max3, in_max[3]);
|
||||
show_in(max4, in_max[4]);
|
||||
show_in(max5, in_max[5]);
|
||||
show_in(max6, in_max[6]);
|
||||
show_in(input0, in[0]);
|
||||
show_in(input1, in[1]);
|
||||
show_in(input2, in[2]);
|
||||
show_in(input3, in[3]);
|
||||
show_in(input4, in[4]);
|
||||
show_in(input5, in[5]);
|
||||
show_in(input6, in[6]);
|
||||
|
||||
#define set_in(suffix, value, reg) \
|
||||
static ssize_t set_in_##suffix(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct lm80_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtol(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock);\
|
||||
data->value = IN_TO_REG(val); \
|
||||
lm80_write_value(client, reg, data->value); \
|
||||
up(&data->update_lock);\
|
||||
return count; \
|
||||
}
|
||||
set_in(min0, in_min[0], LM80_REG_IN_MIN(0));
|
||||
set_in(min1, in_min[1], LM80_REG_IN_MIN(1));
|
||||
set_in(min2, in_min[2], LM80_REG_IN_MIN(2));
|
||||
set_in(min3, in_min[3], LM80_REG_IN_MIN(3));
|
||||
set_in(min4, in_min[4], LM80_REG_IN_MIN(4));
|
||||
set_in(min5, in_min[5], LM80_REG_IN_MIN(5));
|
||||
set_in(min6, in_min[6], LM80_REG_IN_MIN(6));
|
||||
set_in(max0, in_max[0], LM80_REG_IN_MAX(0));
|
||||
set_in(max1, in_max[1], LM80_REG_IN_MAX(1));
|
||||
set_in(max2, in_max[2], LM80_REG_IN_MAX(2));
|
||||
set_in(max3, in_max[3], LM80_REG_IN_MAX(3));
|
||||
set_in(max4, in_max[4], LM80_REG_IN_MAX(4));
|
||||
set_in(max5, in_max[5], LM80_REG_IN_MAX(5));
|
||||
set_in(max6, in_max[6], LM80_REG_IN_MAX(6));
|
||||
|
||||
#define show_fan(suffix, value, div) \
|
||||
static ssize_t show_fan_##suffix(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct lm80_data *data = lm80_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", FAN_FROM_REG(data->value, \
|
||||
DIV_FROM_REG(data->div))); \
|
||||
}
|
||||
show_fan(min1, fan_min[0], fan_div[0]);
|
||||
show_fan(min2, fan_min[1], fan_div[1]);
|
||||
show_fan(input1, fan[0], fan_div[0]);
|
||||
show_fan(input2, fan[1], fan_div[1]);
|
||||
|
||||
#define show_fan_div(suffix, value) \
|
||||
static ssize_t show_fan_div##suffix(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct lm80_data *data = lm80_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->value)); \
|
||||
}
|
||||
show_fan_div(1, fan_div[0]);
|
||||
show_fan_div(2, fan_div[1]);
|
||||
|
||||
#define set_fan(suffix, value, reg, div) \
|
||||
static ssize_t set_fan_##suffix(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct lm80_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtoul(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock);\
|
||||
data->value = FAN_TO_REG(val, DIV_FROM_REG(data->div)); \
|
||||
lm80_write_value(client, reg, data->value); \
|
||||
up(&data->update_lock);\
|
||||
return count; \
|
||||
}
|
||||
set_fan(min1, fan_min[0], LM80_REG_FAN_MIN(1), fan_div[0]);
|
||||
set_fan(min2, fan_min[1], LM80_REG_FAN_MIN(2), fan_div[1]);
|
||||
|
||||
/* 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 suprise; 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)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm80_data *data = i2c_get_clientdata(client);
|
||||
unsigned long min, val = simple_strtoul(buf, NULL, 10);
|
||||
u8 reg;
|
||||
|
||||
/* Save fan_min */
|
||||
down(&data->update_lock);
|
||||
min = FAN_FROM_REG(data->fan_min[nr],
|
||||
DIV_FROM_REG(data->fan_div[nr]));
|
||||
|
||||
switch (val) {
|
||||
case 1: data->fan_div[nr] = 0; break;
|
||||
case 2: data->fan_div[nr] = 1; break;
|
||||
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 "
|
||||
"supported. Choose one of 1, 2, 4 or 8!\n", val);
|
||||
up(&data->update_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
reg = (lm80_read_value(client, LM80_REG_FANDIV) & ~(3 << (2 * (nr + 1))))
|
||||
| (data->fan_div[nr] << (2 * (nr + 1)));
|
||||
lm80_write_value(client, LM80_REG_FANDIV, reg);
|
||||
|
||||
/* Restore fan_min */
|
||||
data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr]));
|
||||
lm80_write_value(client, LM80_REG_FAN_MIN(nr + 1), data->fan_min[nr]);
|
||||
up(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#define set_fan_div(number) \
|
||||
static ssize_t set_fan_div##number(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
return set_fan_div(dev, buf, count, number - 1); \
|
||||
}
|
||||
set_fan_div(1);
|
||||
set_fan_div(2);
|
||||
|
||||
static ssize_t show_temp_input1(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct lm80_data *data = lm80_update_device(dev);
|
||||
return sprintf(buf, "%ld\n", TEMP_FROM_REG(data->temp));
|
||||
}
|
||||
|
||||
#define show_temp(suffix, value) \
|
||||
static ssize_t show_temp_##suffix(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct lm80_data *data = lm80_update_device(dev); \
|
||||
return sprintf(buf, "%d\n", TEMP_LIMIT_FROM_REG(data->value)); \
|
||||
}
|
||||
show_temp(hot_max, temp_hot_max);
|
||||
show_temp(hot_hyst, temp_hot_hyst);
|
||||
show_temp(os_max, temp_os_max);
|
||||
show_temp(os_hyst, temp_os_hyst);
|
||||
|
||||
#define set_temp(suffix, value, reg) \
|
||||
static ssize_t set_temp_##suffix(struct device *dev, struct device_attribute *attr, const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
struct i2c_client *client = to_i2c_client(dev); \
|
||||
struct lm80_data *data = i2c_get_clientdata(client); \
|
||||
long val = simple_strtoul(buf, NULL, 10); \
|
||||
\
|
||||
down(&data->update_lock); \
|
||||
data->value = TEMP_LIMIT_TO_REG(val); \
|
||||
lm80_write_value(client, reg, data->value); \
|
||||
up(&data->update_lock); \
|
||||
return count; \
|
||||
}
|
||||
set_temp(hot_max, temp_hot_max, LM80_REG_TEMP_HOT_MAX);
|
||||
set_temp(hot_hyst, temp_hot_hyst, LM80_REG_TEMP_HOT_HYST);
|
||||
set_temp(os_max, temp_os_max, LM80_REG_TEMP_OS_MAX);
|
||||
set_temp(os_hyst, temp_os_hyst, LM80_REG_TEMP_OS_HYST);
|
||||
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct lm80_data *data = lm80_update_device(dev);
|
||||
return sprintf(buf, "%u\n", data->alarms);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(in0_min, S_IWUSR | S_IRUGO, show_in_min0, set_in_min0);
|
||||
static DEVICE_ATTR(in1_min, S_IWUSR | S_IRUGO, show_in_min1, set_in_min1);
|
||||
static DEVICE_ATTR(in2_min, S_IWUSR | S_IRUGO, show_in_min2, set_in_min2);
|
||||
static DEVICE_ATTR(in3_min, S_IWUSR | S_IRUGO, show_in_min3, set_in_min3);
|
||||
static DEVICE_ATTR(in4_min, S_IWUSR | S_IRUGO, show_in_min4, set_in_min4);
|
||||
static DEVICE_ATTR(in5_min, S_IWUSR | S_IRUGO, show_in_min5, set_in_min5);
|
||||
static DEVICE_ATTR(in6_min, S_IWUSR | S_IRUGO, show_in_min6, set_in_min6);
|
||||
static DEVICE_ATTR(in0_max, S_IWUSR | S_IRUGO, show_in_max0, set_in_max0);
|
||||
static DEVICE_ATTR(in1_max, S_IWUSR | S_IRUGO, show_in_max1, set_in_max1);
|
||||
static DEVICE_ATTR(in2_max, S_IWUSR | S_IRUGO, show_in_max2, set_in_max2);
|
||||
static DEVICE_ATTR(in3_max, S_IWUSR | S_IRUGO, show_in_max3, set_in_max3);
|
||||
static DEVICE_ATTR(in4_max, S_IWUSR | S_IRUGO, show_in_max4, set_in_max4);
|
||||
static DEVICE_ATTR(in5_max, S_IWUSR | S_IRUGO, show_in_max5, set_in_max5);
|
||||
static DEVICE_ATTR(in6_max, S_IWUSR | S_IRUGO, show_in_max6, set_in_max6);
|
||||
static DEVICE_ATTR(in0_input, S_IRUGO, show_in_input0, NULL);
|
||||
static DEVICE_ATTR(in1_input, S_IRUGO, show_in_input1, NULL);
|
||||
static DEVICE_ATTR(in2_input, S_IRUGO, show_in_input2, NULL);
|
||||
static DEVICE_ATTR(in3_input, S_IRUGO, show_in_input3, NULL);
|
||||
static DEVICE_ATTR(in4_input, S_IRUGO, show_in_input4, NULL);
|
||||
static DEVICE_ATTR(in5_input, S_IRUGO, show_in_input5, NULL);
|
||||
static DEVICE_ATTR(in6_input, S_IRUGO, show_in_input6, NULL);
|
||||
static DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min1,
|
||||
set_fan_min1);
|
||||
static DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min2,
|
||||
set_fan_min2);
|
||||
static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input1, NULL);
|
||||
static DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input2, NULL);
|
||||
static DEVICE_ATTR(fan1_div, S_IWUSR | S_IRUGO, show_fan_div1, set_fan_div1);
|
||||
static DEVICE_ATTR(fan2_div, S_IWUSR | S_IRUGO, show_fan_div2, set_fan_div2);
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input1, NULL);
|
||||
static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_hot_max,
|
||||
set_temp_hot_max);
|
||||
static DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_temp_hot_hyst,
|
||||
set_temp_hot_hyst);
|
||||
static DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp_os_max,
|
||||
set_temp_os_max);
|
||||
static DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temp_os_hyst,
|
||||
set_temp_os_hyst);
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static int lm80_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, lm80_detect);
|
||||
}
|
||||
|
||||
int lm80_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
int i, cur;
|
||||
struct i2c_client *new_client;
|
||||
struct lm80_data *data;
|
||||
int err = 0;
|
||||
const char *name;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
/* OK. For now, we presume we have a valid client. We now create the
|
||||
client structure, even though we cannot fill it completely yet.
|
||||
But it allows us to access lm80_{read,write}_value. */
|
||||
if (!(data = kmalloc(sizeof(struct lm80_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct lm80_data));
|
||||
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &lm80_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Now, we do the remaining detection. It is lousy. */
|
||||
if (lm80_read_value(new_client, LM80_REG_ALARM2) & 0xc0)
|
||||
goto error_free;
|
||||
for (i = 0x2a; i <= 0x3d; i++) {
|
||||
cur = i2c_smbus_read_byte_data(new_client, i);
|
||||
if ((i2c_smbus_read_byte_data(new_client, i + 0x40) != cur)
|
||||
|| (i2c_smbus_read_byte_data(new_client, i + 0x80) != cur)
|
||||
|| (i2c_smbus_read_byte_data(new_client, i + 0xc0) != cur))
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
/* Determine the chip type - only one kind supported! */
|
||||
kind = lm80;
|
||||
name = "lm80";
|
||||
|
||||
/* Fill in the remaining client fields and put it into the global list */
|
||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto error_free;
|
||||
|
||||
/* Initialize the LM80 chip */
|
||||
lm80_init_client(new_client);
|
||||
|
||||
/* A few vars need to be filled upon startup */
|
||||
data->fan_min[0] = lm80_read_value(new_client, LM80_REG_FAN_MIN(1));
|
||||
data->fan_min[1] = lm80_read_value(new_client, LM80_REG_FAN_MIN(2));
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in6_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in6_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_in0_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in3_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in4_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in5_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_in6_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_min);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan1_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_fan2_div);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_input);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_max_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_crit);
|
||||
device_create_file(&new_client->dev, &dev_attr_temp1_crit_hyst);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
|
||||
return 0;
|
||||
|
||||
error_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lm80_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev, "Client deregistration failed, "
|
||||
"client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm80_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
/* Called when we have found a new LM80. */
|
||||
static void lm80_init_client(struct i2c_client *client)
|
||||
{
|
||||
/* Reset all except Watchdog values and last conversion values
|
||||
This sets fan-divs to 2, among others. This makes most other
|
||||
initializations unnecessary */
|
||||
lm80_write_value(client, LM80_REG_CONFIG, 0x80);
|
||||
/* Set 11-bit temperature resolution */
|
||||
lm80_write_value(client, LM80_REG_RES, 0x08);
|
||||
|
||||
/* Start monitoring */
|
||||
lm80_write_value(client, LM80_REG_CONFIG, 0x01);
|
||||
}
|
||||
|
||||
static struct lm80_data *lm80_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm80_data *data = i2c_get_clientdata(client);
|
||||
int i;
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) {
|
||||
dev_dbg(&client->dev, "Starting lm80 update\n");
|
||||
for (i = 0; i <= 6; i++) {
|
||||
data->in[i] =
|
||||
lm80_read_value(client, LM80_REG_IN(i));
|
||||
data->in_min[i] =
|
||||
lm80_read_value(client, LM80_REG_IN_MIN(i));
|
||||
data->in_max[i] =
|
||||
lm80_read_value(client, LM80_REG_IN_MAX(i));
|
||||
}
|
||||
data->fan[0] = lm80_read_value(client, LM80_REG_FAN1);
|
||||
data->fan_min[0] =
|
||||
lm80_read_value(client, LM80_REG_FAN_MIN(1));
|
||||
data->fan[1] = lm80_read_value(client, LM80_REG_FAN2);
|
||||
data->fan_min[1] =
|
||||
lm80_read_value(client, LM80_REG_FAN_MIN(2));
|
||||
|
||||
data->temp =
|
||||
(lm80_read_value(client, LM80_REG_TEMP) << 8) |
|
||||
(lm80_read_value(client, LM80_REG_RES) & 0xf0);
|
||||
data->temp_os_max =
|
||||
lm80_read_value(client, LM80_REG_TEMP_OS_MAX);
|
||||
data->temp_os_hyst =
|
||||
lm80_read_value(client, LM80_REG_TEMP_OS_HYST);
|
||||
data->temp_hot_max =
|
||||
lm80_read_value(client, LM80_REG_TEMP_HOT_MAX);
|
||||
data->temp_hot_hyst =
|
||||
lm80_read_value(client, LM80_REG_TEMP_HOT_HYST);
|
||||
|
||||
i = lm80_read_value(client, LM80_REG_FANDIV);
|
||||
data->fan_div[0] = (i >> 2) & 0x03;
|
||||
data->fan_div[1] = (i >> 4) & 0x03;
|
||||
data->alarms = lm80_read_value(client, LM80_REG_ALARM1) +
|
||||
(lm80_read_value(client, LM80_REG_ALARM2) << 8);
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_lm80_init(void)
|
||||
{
|
||||
return i2c_add_driver(&lm80_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_lm80_exit(void)
|
||||
{
|
||||
i2c_del_driver(&lm80_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and "
|
||||
"Philip Edelbrock <phil@netroedge.com>");
|
||||
MODULE_DESCRIPTION("LM80 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_lm80_init);
|
||||
module_exit(sensors_lm80_exit);
|
||||
408
drivers/hwmon/lm83.c
Normal file
408
drivers/hwmon/lm83.c
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* lm83.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
* monitoring
|
||||
* Copyright (C) 2003-2005 Jean Delvare <khali@linux-fr.org>
|
||||
*
|
||||
* Heavily inspired from the lm78, lm75 and adm1021 drivers. The LM83 is
|
||||
* a sensor chip made by National Semiconductor. It reports up to four
|
||||
* temperatures (its own plus up to three external ones) with a 1 deg
|
||||
* resolution and a 3-4 deg accuracy. Complete datasheet can be obtained
|
||||
* from National's website at:
|
||||
* http://www.national.com/pf/LM/LM83.html
|
||||
* Since the datasheet omits to give the chip stepping code, I give it
|
||||
* here: 0x03 (at register 0xff).
|
||||
*
|
||||
* 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/i2c-sensor.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
|
||||
/*
|
||||
* Addresses to scan
|
||||
* Address is selected using 2 three-level pins, resulting in 9 possible
|
||||
* addresses.
|
||||
*/
|
||||
|
||||
static unsigned short normal_i2c[] = { 0x18, 0x19, 0x1a,
|
||||
0x29, 0x2a, 0x2b,
|
||||
0x4c, 0x4d, 0x4e,
|
||||
I2C_CLIENT_END };
|
||||
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
|
||||
|
||||
/*
|
||||
* Insmod parameters
|
||||
*/
|
||||
|
||||
SENSORS_INSMOD_1(lm83);
|
||||
|
||||
/*
|
||||
* The LM83 registers
|
||||
* Manufacturer ID is 0x01 for National Semiconductor.
|
||||
*/
|
||||
|
||||
#define LM83_REG_R_MAN_ID 0xFE
|
||||
#define LM83_REG_R_CHIP_ID 0xFF
|
||||
#define LM83_REG_R_CONFIG 0x03
|
||||
#define LM83_REG_W_CONFIG 0x09
|
||||
#define LM83_REG_R_STATUS1 0x02
|
||||
#define LM83_REG_R_STATUS2 0x35
|
||||
#define LM83_REG_R_LOCAL_TEMP 0x00
|
||||
#define LM83_REG_R_LOCAL_HIGH 0x05
|
||||
#define LM83_REG_W_LOCAL_HIGH 0x0B
|
||||
#define LM83_REG_R_REMOTE1_TEMP 0x30
|
||||
#define LM83_REG_R_REMOTE1_HIGH 0x38
|
||||
#define LM83_REG_W_REMOTE1_HIGH 0x50
|
||||
#define LM83_REG_R_REMOTE2_TEMP 0x01
|
||||
#define LM83_REG_R_REMOTE2_HIGH 0x07
|
||||
#define LM83_REG_W_REMOTE2_HIGH 0x0D
|
||||
#define LM83_REG_R_REMOTE3_TEMP 0x31
|
||||
#define LM83_REG_R_REMOTE3_HIGH 0x3A
|
||||
#define LM83_REG_W_REMOTE3_HIGH 0x52
|
||||
#define LM83_REG_R_TCRIT 0x42
|
||||
#define LM83_REG_W_TCRIT 0x5A
|
||||
|
||||
/*
|
||||
* Conversions and various macros
|
||||
* The LM83 uses signed 8-bit values with LSB = 1 degree Celsius.
|
||||
*/
|
||||
|
||||
#define TEMP_FROM_REG(val) ((val) * 1000)
|
||||
#define TEMP_TO_REG(val) ((val) <= -128000 ? -128 : \
|
||||
(val) >= 127000 ? 127 : \
|
||||
(val) < 0 ? ((val) - 500) / 1000 : \
|
||||
((val) + 500) / 1000)
|
||||
|
||||
static const u8 LM83_REG_R_TEMP[] = {
|
||||
LM83_REG_R_LOCAL_TEMP,
|
||||
LM83_REG_R_REMOTE1_TEMP,
|
||||
LM83_REG_R_REMOTE2_TEMP,
|
||||
LM83_REG_R_REMOTE3_TEMP,
|
||||
LM83_REG_R_LOCAL_HIGH,
|
||||
LM83_REG_R_REMOTE1_HIGH,
|
||||
LM83_REG_R_REMOTE2_HIGH,
|
||||
LM83_REG_R_REMOTE3_HIGH,
|
||||
LM83_REG_R_TCRIT,
|
||||
};
|
||||
|
||||
static const u8 LM83_REG_W_HIGH[] = {
|
||||
LM83_REG_W_LOCAL_HIGH,
|
||||
LM83_REG_W_REMOTE1_HIGH,
|
||||
LM83_REG_W_REMOTE2_HIGH,
|
||||
LM83_REG_W_REMOTE3_HIGH,
|
||||
LM83_REG_W_TCRIT,
|
||||
};
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
*/
|
||||
|
||||
static int lm83_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int lm83_detect(struct i2c_adapter *adapter, int address, int kind);
|
||||
static int lm83_detach_client(struct i2c_client *client);
|
||||
static struct lm83_data *lm83_update_device(struct device *dev);
|
||||
|
||||
/*
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
|
||||
static struct i2c_driver lm83_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm83",
|
||||
.id = I2C_DRIVERID_LM83,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.attach_adapter = lm83_attach_adapter,
|
||||
.detach_client = lm83_detach_client,
|
||||
};
|
||||
|
||||
/*
|
||||
* Client data (each client gets its own)
|
||||
*/
|
||||
|
||||
struct lm83_data {
|
||||
struct i2c_client client;
|
||||
struct semaphore update_lock;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
|
||||
/* registers values */
|
||||
s8 temp[9]; /* 0..3: input 1-4,
|
||||
4..7: high limit 1-4,
|
||||
8 : critical limit */
|
||||
u16 alarms; /* bitvector, combined */
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
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 lm83_data *data = lm83_update_device(dev);
|
||||
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index]));
|
||||
}
|
||||
|
||||
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 lm83_data *data = i2c_get_clientdata(client);
|
||||
long val = simple_strtol(buf, NULL, 10);
|
||||
int nr = attr->index;
|
||||
|
||||
down(&data->update_lock);
|
||||
data->temp[nr] = TEMP_TO_REG(val);
|
||||
i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr - 4],
|
||||
data->temp[nr]);
|
||||
up(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy,
|
||||
char *buf)
|
||||
{
|
||||
struct lm83_data *data = lm83_update_device(dev);
|
||||
return sprintf(buf, "%d\n", data->alarms);
|
||||
}
|
||||
|
||||
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);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp,
|
||||
set_temp, 4);
|
||||
static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp,
|
||||
set_temp, 5);
|
||||
static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp,
|
||||
set_temp, 6);
|
||||
static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_temp,
|
||||
set_temp, 7);
|
||||
static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL, 8);
|
||||
static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp, NULL, 8);
|
||||
static SENSOR_DEVICE_ATTR(temp3_crit, S_IWUSR | S_IRUGO, show_temp,
|
||||
set_temp, 8);
|
||||
static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp, NULL, 8);
|
||||
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static int lm83_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
||||
return 0;
|
||||
return i2c_detect(adapter, &addr_data, lm83_detect);
|
||||
}
|
||||
|
||||
/*
|
||||
* The following function does more than just detection. If detection
|
||||
* succeeds, it also registers the new chip.
|
||||
*/
|
||||
static int lm83_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct i2c_client *new_client;
|
||||
struct lm83_data *data;
|
||||
int err = 0;
|
||||
const char *name = "";
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto exit;
|
||||
|
||||
if (!(data = kmalloc(sizeof(struct lm83_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
memset(data, 0, sizeof(struct lm83_data));
|
||||
|
||||
/* The common I2C client data is placed right after the
|
||||
* LM83-specific data. */
|
||||
new_client = &data->client;
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &lm83_driver;
|
||||
new_client->flags = 0;
|
||||
|
||||
/* Now we do the detection and identification. 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, LM83).
|
||||
* 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. */
|
||||
|
||||
/* Default to an LM83 if forced */
|
||||
if (kind == 0)
|
||||
kind = lm83;
|
||||
|
||||
if (kind < 0) { /* detection */
|
||||
if (((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1)
|
||||
& 0xA8) != 0x00) ||
|
||||
((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2)
|
||||
& 0x48) != 0x00) ||
|
||||
((i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG)
|
||||
& 0x41) != 0x00)) {
|
||||
dev_dbg(&adapter->dev,
|
||||
"LM83 detection failed at 0x%02x.\n", address);
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind <= 0) { /* identification */
|
||||
u8 man_id, chip_id;
|
||||
|
||||
man_id = i2c_smbus_read_byte_data(new_client,
|
||||
LM83_REG_R_MAN_ID);
|
||||
chip_id = i2c_smbus_read_byte_data(new_client,
|
||||
LM83_REG_R_CHIP_ID);
|
||||
|
||||
if (man_id == 0x01) { /* National Semiconductor */
|
||||
if (chip_id == 0x03) {
|
||||
kind = lm83;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind <= 0) { /* identification failed */
|
||||
dev_info(&adapter->dev,
|
||||
"Unsupported chip (man_id=0x%02X, "
|
||||
"chip_id=0x%02X).\n", man_id, chip_id);
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == lm83) {
|
||||
name = "lm83";
|
||||
}
|
||||
|
||||
/* We can fill in the remaining client fields */
|
||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||
data->valid = 0;
|
||||
init_MUTEX(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
goto exit_free;
|
||||
|
||||
/*
|
||||
* Initialize the LM83 chip
|
||||
* (Nothing to do for this one.)
|
||||
*/
|
||||
|
||||
/* Register sysfs hooks */
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp1_input.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp2_input.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp3_input.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp4_input.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp1_max.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp2_max.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp3_max.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp4_max.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp1_crit.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp2_crit.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp3_crit.dev_attr);
|
||||
device_create_file(&new_client->dev,
|
||||
&sensor_dev_attr_temp4_crit.dev_attr);
|
||||
device_create_file(&new_client->dev, &dev_attr_alarms);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lm83_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = i2c_detach_client(client))) {
|
||||
dev_err(&client->dev,
|
||||
"Client deregistration failed, client not detached.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
kfree(i2c_get_clientdata(client));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct lm83_data *lm83_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lm83_data *data = i2c_get_clientdata(client);
|
||||
|
||||
down(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) {
|
||||
int nr;
|
||||
|
||||
dev_dbg(&client->dev, "Updating lm83 data.\n");
|
||||
for (nr = 0; nr < 9; nr++) {
|
||||
data->temp[nr] =
|
||||
i2c_smbus_read_byte_data(client,
|
||||
LM83_REG_R_TEMP[nr]);
|
||||
}
|
||||
data->alarms =
|
||||
i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1)
|
||||
+ (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2)
|
||||
<< 8);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
up(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init sensors_lm83_init(void)
|
||||
{
|
||||
return i2c_add_driver(&lm83_driver);
|
||||
}
|
||||
|
||||
static void __exit sensors_lm83_exit(void)
|
||||
{
|
||||
i2c_del_driver(&lm83_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
|
||||
MODULE_DESCRIPTION("LM83 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sensors_lm83_init);
|
||||
module_exit(sensors_lm83_exit);
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue