mirror of
https://github.com/lm-sensors/lm-sensors.git
synced 2026-10-14 10:29:31 +00:00
git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@2539 7894878c-1315-0410-8ee3-d5d059ff63e0
421 lines
11 KiB
C
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);
|
|
|