Files
lm-sensors/kernel/chips/lm92.c
T
2004-05-16 16:13:07 +00:00

421 lines
11 KiB
C

/*
* LM92 - Part of lm_sensors, Linux kernel modules for hardware
* monitoring
*
* Author: Abraham van der Merwe <abraham@2d3d.co.za>
*
* Linux support for the National Semiconductor LM92 Temperature
* Sensor.
*
* Based on code from the lm-sensors project which is available
* at http://www.lm-sensors.nu/. lm87.c have been particularly
* helpful (:
*
* This source code is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#include <asm/semaphore.h>
#include "version.h"
/* if defined, 4 faults must occur consecutively to set alarm flags */
/* #define ENABLE_FAULT_QUEUE */
#define LM92_REG_TEMPERATURE 0x00 /* ro, 16-bit */
#define LM92_REG_CONFIGURATION 0x01 /* rw, 8-bit */
#define LM92_REG_TRIP_HYSTERESIS 0x02 /* rw, 16-bit */
#define LM92_REG_TRIP_CRITICAL 0x03 /* rw, 16-bit */
#define LM92_REG_TRIP_LOW 0x04 /* rw, 16-bit */
#define LM92_REG_TRIP_HIGH 0x05 /* rw, 16-bit */
#define LM92_REG_MANUFACTURER 0x07 /* ro, 16-bit */
#define LM92_MANUFACTURER_ID 0x8001
#define TEMP_MIN (-4096)
#define TEMP_MAX 4095
#define LIMIT(x) do { \
if ((x) < TEMP_MIN) (x) = TEMP_MIN; \
if ((x) > TEMP_MAX) (x) = TEMP_MAX; \
} while (0)
#define PROC_TO_NATIVE(x) ((x) / 625)
#define NATIVE_TO_PROC(x) ((x) * 625)
#define CELSIUS(x) ((x) * 16)
static void lm92_temp (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results);
static void lm92_alarms (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results);
/* -- SENSORS SYSCTL START -- */
#define LM92_SYSCTL_ALARMS 2001 /* high, low, critical */
#define LM92_SYSCTL_TEMP 1200 /* high, low, critical, hysteresis, input */
#define LM92_ALARM_TEMP_HIGH 0x01
#define LM92_ALARM_TEMP_LOW 0x02
#define LM92_ALARM_TEMP_CRIT 0x04
#define LM92_TEMP_HIGH 0x08
#define LM92_TEMP_LOW 0x10
#define LM92_TEMP_CRIT 0x20
#define LM92_TEMP_HYST 0x40
#define LM92_TEMP_INPUT 0x80
/* -- SENSORS SYSCTL END -- */
static ctl_table lm92_dir_table[] = {
{LM92_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm92_temp, NULL},
{LM92_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm92_alarms, NULL},
{0}
};
/* NOTE: all temperatures are degrees centigrade * 16 */
typedef struct {
struct i2c_client client;
int sysctl_id;
unsigned long timestamp;
struct {
long high;
long low;
long crit;
long hyst;
long input;
} temp;
struct {
long low;
long high;
long crit;
} alarms;
} lm92_t;
/* this is needed for each client driver method */
static struct i2c_driver lm92_driver;
/* ensure exclusive access to chip and static variables */
static DECLARE_MUTEX (mutex);
/* addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* insmod parameters */
SENSORS_INSMOD_1 (lm92);
static inline int lm92_write8 (struct i2c_client *client,u8 reg,u8 value)
{
return (i2c_smbus_write_byte_data (client,reg,value) < 0 ? -EIO : 0);
}
static inline int lm92_read16 (struct i2c_client *client,u8 reg,u16 *value)
{
s32 tmp = i2c_smbus_read_word_data (client,reg);
if (tmp < 0) return (-EIO);
/* convert the data to little endian format */
*value = swab16((u16) tmp);
return (0);
}
static inline int lm92_write16 (struct i2c_client *client,u8 reg,u16 value)
{
/* convert the data to big endian format */
if (i2c_smbus_write_word_data(client, reg, swab16(value)) < 0)
return -EIO;
return 0;
}
static int lm92_read (struct i2c_client *client)
{
lm92_t *data = (lm92_t *) client->data;
u16 value[5];
if ((jiffies - data->timestamp) > HZ) {
if (lm92_read16 (client,LM92_REG_TEMPERATURE,value) < 0 ||
lm92_read16 (client,LM92_REG_TRIP_HYSTERESIS,value + 1) < 0 ||
lm92_read16 (client,LM92_REG_TRIP_CRITICAL,value + 2) < 0 ||
lm92_read16 (client,LM92_REG_TRIP_LOW,value + 3) < 0 ||
lm92_read16 (client,LM92_REG_TRIP_HIGH,value + 4) < 0)
return (-EIO);
data->temp.input = (s16) value[0] >> 3;
data->temp.hyst = (s16) value[1] >> 3;
data->temp.crit = (s16) value[2] >> 3;
data->temp.low = (s16) value[3] >> 3;
data->temp.high = (s16) value[4] >> 3;
data->alarms.low = value[0] & 1;
data->alarms.high = (value[0] & 2) >> 1;
data->alarms.crit = (value[0] & 4) >> 2;
data->timestamp = jiffies;
}
return (0);
}
static int lm92_write (struct i2c_client *client)
{
lm92_t *data = (lm92_t *) client->data;
LIMIT (data->temp.hyst);
LIMIT (data->temp.crit);
LIMIT (data->temp.low);
LIMIT (data->temp.high);
if (lm92_write16 (client,LM92_REG_TRIP_HYSTERESIS,((s16) data->temp.hyst << 3)) < 0 ||
lm92_write16 (client,LM92_REG_TRIP_CRITICAL,((s16) data->temp.crit << 3)) < 0 ||
lm92_write16 (client,LM92_REG_TRIP_LOW,((s16) data->temp.low << 3)) < 0 ||
lm92_write16 (client,LM92_REG_TRIP_HIGH,((s16) data->temp.high << 3)) < 0)
return (-EIO);
return (0);
}
static void lm92_temp (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results)
{
if (!down_interruptible (&mutex)) {
lm92_t *data = (lm92_t *) client->data;
if (operation == SENSORS_PROC_REAL_READ) {
lm92_read (client);
results[0] = NATIVE_TO_PROC (data->temp.input);
results[1] = NATIVE_TO_PROC (data->temp.high);
results[2] = NATIVE_TO_PROC (data->temp.low);
results[3] = NATIVE_TO_PROC (data->temp.crit);
results[4] = NATIVE_TO_PROC (data->temp.hyst);
*nrels_mag = 5;
} else if (operation == SENSORS_PROC_REAL_WRITE && *nrels_mag == 4) {
data->temp.high = PROC_TO_NATIVE (results[0]);
data->temp.low = PROC_TO_NATIVE (results[1]);
data->temp.crit = PROC_TO_NATIVE (results[2]);
data->temp.hyst = PROC_TO_NATIVE (results[3]);
lm92_write (client);
} else if (operation == SENSORS_PROC_REAL_INFO) {
*nrels_mag = 4;
}
up (&mutex);
}
}
static void lm92_alarms (struct i2c_client *client,int operation,int ctl_name,int *nrels_mag,long *results)
{
if (!down_interruptible (&mutex)) {
lm92_t *data = (lm92_t *) client->data;
if (operation == SENSORS_PROC_REAL_READ) {
lm92_read (client);
results[0] = data->alarms.high || (data->alarms.low << 1) || (data->alarms.crit << 2);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_INFO) {
*nrels_mag = 0;
}
up (&mutex);
}
}
static int max6635_check(struct i2c_client *client)
{
int i;
u16 temp_low, temp_high, temp_hyst, temp_crit;
u8 conf;
temp_low = i2c_smbus_read_word_data(client, LM92_REG_TRIP_LOW);
temp_high = i2c_smbus_read_word_data(client, LM92_REG_TRIP_HIGH);
temp_hyst = i2c_smbus_read_word_data(client, LM92_REG_TRIP_HYSTERESIS);
temp_crit = i2c_smbus_read_word_data(client, LM92_REG_TRIP_CRITICAL);
if ((temp_low & 0x7f00) || (temp_high & 0x7f00)
|| (temp_hyst & 0x7f00) || (temp_crit & 0x7f00))
return 0;
conf = i2c_smbus_read_byte_data(client, LM92_REG_CONFIGURATION);
for (i=0; i<128; i+=16) {
if (temp_low != i2c_smbus_read_word_data(client, LM92_REG_TRIP_LOW + i)
|| temp_high != i2c_smbus_read_word_data(client, LM92_REG_TRIP_HIGH + i)
|| temp_hyst != i2c_smbus_read_word_data(client, LM92_REG_TRIP_HYSTERESIS + i)
|| temp_crit != i2c_smbus_read_word_data(client, LM92_REG_TRIP_CRITICAL + i)
|| conf != i2c_smbus_read_byte_data(client, LM92_REG_CONFIGURATION + i))
return 0;
}
return 1;
}
static int lm92_init_client (struct i2c_client *client)
{
lm92_t *data = (lm92_t *) client->data;
u8 value = 0;
int result;
/* force reads to query the chip */
data->timestamp = 0;
/* setup the configuration register */
#ifdef ENABLE_FAULT_QUEUE
value |= 0x10;
#endif /* #ifdef ENABLE_FAULT_QUEUE */
if (lm92_write8 (client,LM92_REG_CONFIGURATION,value) < 0)
return (-ENODEV);
/* set default alarm trigger values */
data->temp.high = CELSIUS (64);
data->temp.low = CELSIUS (10);
data->temp.crit = CELSIUS (80);
data->temp.hyst = CELSIUS (2);
if ((result = lm92_write (client)) < 0)
return (result);
/* read everything once so that our cached data is updated */
if ((result = lm92_read (client)) < 0)
return (result);
return (0);
}
static int lm92_detect (struct i2c_adapter *adapter,int address,unsigned short flags,int kind)
{
static int id = 0;
struct i2c_client *client;
lm92_t *data;
int result;
u16 manufacturer;
if (!i2c_check_functionality (adapter,I2C_FUNC_SMBUS_BYTE_DATA))
return (-ENODEV);
if (!(data = kmalloc(sizeof(lm92_t), GFP_KERNEL)))
return (-ENOMEM);
client = &data->client;
client->addr = address;
client->data = data;
client->adapter = adapter;
client->driver = &lm92_driver;
client->flags = 0;
strcpy (client->name,lm92_driver.name);
if (down_interruptible (&mutex)) {
result = -ERESTARTSYS;
goto ERROR1;
}
if (kind < 0) {
/* Is it an lm92? */
if (address < 0x4c
&& (lm92_read16(client,LM92_REG_MANUFACTURER,&manufacturer) < 0
|| manufacturer != LM92_MANUFACTURER_ID)) {
/* Is it a MAX6635/MAX6635/MAX6635? */
if (!max6635_check(client)) {
result = -ENODEV;
goto ERROR2;
}
}
}
if ((result = i2c_attach_client (client))) {
goto ERROR2;
}
if ((result = i2c_register_entry (client,client->name,lm92_dir_table)) < 0) {
goto ERROR3;
}
data->sysctl_id = result;
if ((result = lm92_init_client (client)) < 0) {
goto ERROR4;
}
client->id = id++;
up (&mutex);
return (0);
ERROR4:
i2c_deregister_entry(data->sysctl_id);
ERROR3:
i2c_detach_client(client);
ERROR2:
up(&mutex);
ERROR1:
kfree(data);
return result;
}
static int lm92_attach_adapter (struct i2c_adapter *adapter)
{
return i2c_detect (adapter,&addr_data,lm92_detect);
}
static int lm92_detach_client (struct i2c_client *client)
{
int result;
i2c_deregister_entry (((lm92_t *) (client->data))->sysctl_id);
if ((result = i2c_detach_client (client)))
return (result);
kfree(client->data);
return (0);
}
static struct i2c_driver lm92_driver = {
.owner = THIS_MODULE,
.name = "lm92",
.id = I2C_DRIVERID_LM92,
.flags = I2C_DF_NOTIFY,
.attach_adapter = lm92_attach_adapter,
.detach_client = lm92_detach_client,
};
static int __init sm_lm92_init(void)
{
printk ("lm92.o version %s (%s)\n",LM_VERSION,LM_DATE);
return i2c_add_driver(&lm92_driver);
}
static void __exit sm_lm92_exit(void)
{
i2c_del_driver(&lm92_driver);
}
MODULE_AUTHOR ("Abraham van der Merwe <abraham@2d3d.co.za>");
MODULE_DESCRIPTION ("Linux support for LM92 Temperature Sensor");
MODULE_LICENSE ("GPL");
module_init(sm_lm92_init);
module_exit(sm_lm92_exit);