diff --git a/kernel/chips/thmc50.c b/kernel/chips/thmc50.c new file mode 100644 index 00000000..57fa5a3c --- /dev/null +++ b/kernel/chips/thmc50.c @@ -0,0 +1,423 @@ +/* + thmc50.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard and + Philip Edelbrock + + 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 +#include +#include +#include "sensors.h" +#include "i2c-isa.h" +#include "version.h" +#include "compat.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,53) +#include +#else +#define __init +#define __initdata +#endif + + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {SENSORS_I2C_END}; +static unsigned short normal_i2c_range[] = {0x2D,0x2F,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(thmc50); + +/* Many THMC50 constants specified below */ + +/* The THMC50 registers */ +#define THMC50_REG_TEMP 0x27 +#define THMC50_REG_CONF 0x40 +#define THMC50_REG_TEMP_HYST 0x3A +#define THMC50_REG_TEMP_OS 0x39 + +/* 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 TEMP_FROM_REG(val) ((val>127)?val - 0x0100:val) +#define TEMP_TO_REG(val) ((val<0)?0x0100+val:val) + +/* Initial values */ +#define THMC50_INIT_TEMP_OS 60 +#define THMC50_INIT_TEMP_HYST 50 + +/* Each client has this additional data */ +struct thmc50_data { + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u16 temp,temp_os,temp_hyst; /* Register values */ +}; + +#ifdef MODULE +extern int init_module(void); +extern int cleanup_module(void); +#endif /* MODULE */ + +#ifdef MODULE +static +#else +extern +#endif + int __init sensors_thmc50_init(void); +static int __init thmc50_cleanup(void); +static int thmc50_attach_adapter(struct i2c_adapter *adapter); +static int thmc50_detect(struct i2c_adapter *adapter, int address, int kind); +static void thmc50_init_client(struct i2c_client *client); +static int thmc50_detach_client(struct i2c_client *client); +static int thmc50_command(struct i2c_client *client, unsigned int cmd, + void *arg); +static void thmc50_inc_use (struct i2c_client *client); +static void thmc50_dec_use (struct i2c_client *client); +static u16 swap_bytes(u16 val); +static int thmc50_read_value(struct i2c_client *client, u8 reg); +static int thmc50_write_value(struct i2c_client *client, u8 reg, u16 value); +static void thmc50_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void thmc50_update_client(struct i2c_client *client); + + +/* This is the driver that will be inserted */ +static struct i2c_driver thmc50_driver = { + /* name */ "THMC50 sensor chip driver", + /* id */ I2C_DRIVERID_THMC50, + /* flags */ I2C_DF_NOTIFY, + /* attach_adapter */ &thmc50_attach_adapter, + /* detach_client */ &thmc50_detach_client, + /* command */ &thmc50_command, + /* inc_use */ &thmc50_inc_use, + /* dec_use */ &thmc50_dec_use +}; + +/* These files are created for each detected THMC50. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table thmc50_dir_table_template[] = { + { THMC50_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &sensors_proc_real, + &sensors_sysctl_real, NULL, &thmc50_temp }, + { 0 } +}; + +/* Used by init/cleanup */ +static int __initdata thmc50_initialized = 0; + +/* I choose here for semi-static THMC50 allocation. Complete dynamic + allocation could also be used; the code needed for this would probably + take more memory than the datastructure takes now. */ +#define MAX_THMC50_NR 16 +static struct i2c_client *thmc50_list[MAX_THMC50_NR]; + + +int thmc50_attach_adapter(struct i2c_adapter *adapter) +{ + return sensors_detect(adapter,&addr_data,thmc50_detect); +} + +/* This function is called by sensors_detect */ +int thmc50_detect(struct i2c_adapter *adapter, int address, int kind) +{ + int company; + struct i2c_client *new_client; + struct thmc50_data *data; + int err=0; + const char *type_name,*client_name; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; sensors_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk("thmc50.o: thmc50_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + /* Here, we have to do the address registration check for the I2C bus. + But that is not yet implemented. */ + + /* 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 thmc50_{read,write}_value. */ + if (! (new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct thmc50_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct thmc50_data *) (((struct i2c_client *) new_client) + 1); + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &thmc50_driver; + + /* Now, we do the remaining detection. */ + company = i2c_smbus_read_byte_data(adapter,address,0x3E); + = i2c_smbus_read_word_data(adapter,address,2); + os = i2c_smbus_read_word_data(adapter,address,3); + + if (company != 0x4E) + goto ERROR1; + + /* Determine the chip type - only one kind supported! */ + kind = thmc50; + + if (kind == thmc50) { + type_name = "thmc50"; + client_name = "THMC50 chip"; + } else { +#ifdef DEBUG + printk("thmc50.o: Internal error: unknown kind (%d)?!?",kind); +#endif + goto ERROR1; + } + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name,client_name); + + /* Find a place in our global list */ + for (i = 0; i < MAX_THMC50_NR; i++) + if (! thmc50_list[i]) + break; + if (i == MAX_THMC50_NR) { + err = -ENOMEM; + printk("thmc50.o: No empty slots left, recompile and heighten " + "MAX_THMC50_NR!\n"); + goto ERROR2; + } + thmc50_list[i] = new_client; + new_client->id = i; + 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 ERROR3; + + /* Register a new directory entry with module sensors */ + if ((i = sensors_register_entry(new_client,type_name, + thmc50_dir_table_template)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + thmc50_init_client((struct i2c_client *) new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + +ERROR4: + i2c_detach_client(new_client); +ERROR3: + for (i = 0; i < MAX_THMC50_NR; i++) + if (new_client == thmc50_list[i]) + thmc50_list[i] = NULL; +ERROR2: +ERROR1: + kfree(new_client); +ERROR0: + return err; +} + +int thmc50_detach_client(struct i2c_client *client) +{ + int err,i; + + sensors_deregister_entry(((struct thmc50_data *)(client->data))->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk("thmc50.o: Client deregistration failed, client not detached.\n"); + return err; + } + + for (i = 0; i < MAX_THMC50_NR; i++) + if (client == thmc50_list[i]) + break; + if ((i == MAX_THMC50_NR)) { + printk("thmc50.o: Client to detach not found.\n"); + return -ENOENT; + } + thmc50_list[i] = NULL; + + kfree(client); + + return 0; +} + + +/* No commands defined yet */ +int thmc50_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + return 0; +} + +void thmc50_inc_use (struct i2c_client *client) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +void thmc50_dec_use (struct i2c_client *client) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +u16 swap_bytes(u16 val) +{ + return (val >> 8) | (val << 8); +} + +/* All registers are word-sized, except for the configuration register. + THMC50 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +int thmc50_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client->adapter,client->addr,reg); +} + +/* All registers are word-sized, except for the configuration register. + THMC50 uses a high-byte first convention, which is exactly opposite to + the usual practice. */ +int thmc50_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + return i2c_smbus_write_byte_data(client->adapter,client->addr,reg,value); +} + +void thmc50_init_client(struct i2c_client *client) +{ + /* Initialize the THMC50 chip */ + thmc50_write_value(client,THMC50_REG_TEMP_OS, + TEMP_TO_REG(THMC50_INIT_TEMP_OS)); + thmc50_write_value(client,THMC50_REG_TEMP_HYST, + TEMP_TO_REG(THMC50_INIT_TEMP_HYST)); + thmc50_write_value(client,THMC50_REG_CONF,1); +} + +void thmc50_update_client(struct i2c_client *client) +{ + struct thmc50_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ+HZ/2 ) || + (jiffies < data->last_updated) || ! data->valid) { + +#ifdef DEBUG + printk("Starting thmc50 update\n"); +#endif + + data->temp = thmc50_read_value(client,THMC50_REG_TEMP); + data->temp_os = thmc50_read_value(client,THMC50_REG_TEMP_OS); + data->temp_hyst = thmc50_read_value(client,THMC50_REG_TEMP_HYST); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void thmc50_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct thmc50_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + thmc50_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_os); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->temp_os = TEMP_TO_REG(results[0]); + thmc50_write_value(client,THMC50_REG_TEMP_OS,data->temp_os); + } + if (*nrels_mag >= 2) { + data->temp_os = TEMP_TO_REG(results[1]); + thmc50_write_value(client,THMC50_REG_TEMP_HYST,data->temp_os); + } + } +} + +int __init sensors_thmc50_init(void) +{ + int res; + + printk("thmc50.o version %s (%s)\n",LM_VERSION,LM_DATE); + thmc50_initialized = 0; + if ((res = i2c_add_driver(&thmc50_driver))) { + printk("thmc50.o: Driver registration failed, module not inserted.\n"); + thmc50_cleanup(); + return res; + } + thmc50_initialized ++; + return 0; +} + +int __init thmc50_cleanup(void) +{ + int res; + + if (thmc50_initialized >= 1) { + if ((res = i2c_del_driver(&thmc50_driver))) { + printk("thmc50.o: Driver deregistration failed, module not removed.\n"); + return res; + } + thmc50_initialized --; + } + + return 0; +} + +EXPORT_NO_SYMBOLS; + +#ifdef MODULE + +MODULE_AUTHOR("Frodo Looijaard and Philip Edelbrock "); +MODULE_DESCRIPTION("THMC50 driver"); + +int init_module(void) +{ + return sensors_thmc50_init(); +} + +int cleanup_module(void) +{ + return thmc50_cleanup(); +} + +#endif /* MODULE */ +