/* * LM92 - Part of lm_sensors, Linux kernel modules for hardware * monitoring * * Author: Abraham van der Merwe * * 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 #include #include #include #include #include #include #include #include #include #include #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) { struct i2c_client *client; lm92_t *data; int result = 0; u16 manufacturer; if (!i2c_check_functionality (adapter,I2C_FUNC_SMBUS_BYTE_DATA)) return 0; 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)) { goto ERROR2; } } } if ((result = i2c_attach_client (client))) { goto ERROR2; } if ((result = i2c_register_entry(client, client->name, lm92_dir_table, THIS_MODULE)) < 0) { goto ERROR3; } data->sysctl_id = result; if ((result = lm92_init_client (client)) < 0) { goto ERROR4; } 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 = { .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 "); MODULE_DESCRIPTION ("Linux support for LM92 Temperature Sensor"); MODULE_LICENSE ("GPL"); module_init(sm_lm92_init); module_exit(sm_lm92_exit);