From 6ea68ba7a25abdb26fcf811af996fa142c15f296 Mon Sep 17 00:00:00 2001 From: Frodo Looijaard Date: Tue, 1 Dec 1998 21:11:32 +0000 Subject: [PATCH] Many things, most notable the lm78 module * Some Makefile changes. doc/makefiles documents most of them. * The lm78 module now compiles, but it will still crash. * New module sensors, with general code usable by chip driver modules * i2c-core: added function i2c_adapter_id(), which returns a (low) unique i2c-bus ID. * lm78.h stuff moved into lm78.c or sensors.h git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@19 7894878c-1315-0410-8ee3-d5d059ff63e0 --- TODO | 5 +- doc/makefiles | 54 +++ i2c/MODIFICATIONS | 2 + i2c/Module.mk | 9 +- i2c/detect/Module.mk | 6 +- i2c/drivers/Module.mk | 9 +- i2c/eeprom/Module.mk | 6 +- i2c/i2c-core.c | 12 +- i2c/i2c.h | 4 + kernel/Module.mk | 15 +- kernel/chips/lm78.c | 706 ++++++++++++++++++++++++++++++++++----- kernel/compat.h | 16 + kernel/include/compat.h | 16 + kernel/include/sensors.h | 37 ++ kernel/sensors.c | 346 +++++++++++++++++++ src/Module.mk | 15 +- src/compat.h | 16 + src/lm78.c | 706 ++++++++++++++++++++++++++++++++++----- src/lm78.h | 30 -- src/sensors.c | 346 +++++++++++++++++++ src/sensors.h | 37 ++ 21 files changed, 2169 insertions(+), 224 deletions(-) create mode 100644 doc/makefiles create mode 100644 kernel/sensors.c delete mode 100644 src/lm78.h create mode 100644 src/sensors.c diff --git a/TODO b/TODO index 69376fcb..4766256d 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,14 @@ Many, many things. Most notably: +* 2.1 kernel tests; at this moment, only 2.0 compiles have been tried. +* Make it SMP-safe: there are no spinlocks yet. They are needed at many + places, probably (everywhere where global vars are accessed). * Write smbus_access_i2c in smbus.c * Write piix4_* in piix4.c * Complete lm78 driver * Extend dev interface for SMBus * Create entries in /proc/bus: a list of all registered busses, and for - each bus, a file with registered clients. + each bus, a file with registered clients (partially done) * Write all other drivers * How to make drivers detect thing *not*; for example, if a Winbond is present, the LM75 driver should not try to access its simulated diff --git a/doc/makefiles b/doc/makefiles new file mode 100644 index 00000000..67508065 --- /dev/null +++ b/doc/makefiles @@ -0,0 +1,54 @@ +The Makefiles in this package are rather advanced. They are partially based +on the article "Recursive Make considered Harmful", written by Peter Miller. +See doc/useful_addresses.html for a link to this article. + +There is one big Makefile in the root of this package. It includes many +other files; one for each directory in which source code is found. These +included files are called 'Module.mk'. There is a separate set of Makefiles +in the i2c/ directories; these are Simon Vogl's original Makefiles, and +not referred by the main package Makefile. + +There are several interesting targets defined through this Makefile: + * all + Create everything in all directories. + * all-i2c, all-src, ... + Create everything in the designated directory. + * install + Install everything from all directories. + * install-i2c, install-src, ... + Install everything in the designated directory. + * clean + Remove anything which can be regenerated from all directories. A call + of 'make clean' (without any other targets) will ignore any .d files; + this is useful when they are out of date (and prevent the calling of + any other target). + * clean-i2c, clean-src, ... + Remove anything which can be regenerated from the designated directory. + * dep + Do nothing, but generate any missing .d files. The command + 'make clean && make dep' canbe useful in this case ('make clean dep' + will not work, due to technical reasons!). + * version + Regenerate version.h, using the current date for the date-stamp, and + a user-supplied version number. + * package + Create a .tar.gz file containing everything except the CVS directories. + * src/lm78.o, i2c/i2c-core.o, ... + You can of course also specify one or more targets to make. + +The best way to understand the Module.mk subfiles is to examine one of them, +for example src/Module.mk. They are not too difficult to understand. + +There are several variables which can be set in the main Makefile. You can +also specify them on the command-line; this overrules any definitions +within the Makefile. For example: 'make all WARN=1' will enable all warnings. +Examine main Makefile to see which ones are available. The most important +ones for developers: + * WARN + Set to 1 to enable many compiler warnings. + * DEBUG + Set to 1 to enable any debugging code. Note that debugging code should + only output more information, and never make the code mis-behave. + +There are lots of comments within the main Makefile. Please read them if +you want to know more. diff --git a/i2c/MODIFICATIONS b/i2c/MODIFICATIONS index 560a31cd..ddf04e20 100644 --- a/i2c/MODIFICATIONS +++ b/i2c/MODIFICATIONS @@ -43,3 +43,5 @@ Here come the changes: i2c_detach_client(client); with: client->driver->detach_client(client); + +! i2c-core: function i2c_adapter_id added diff --git a/i2c/Module.mk b/i2c/Module.mk index eeba8eaa..fc9abb77 100644 --- a/i2c/Module.mk +++ b/i2c/Module.mk @@ -29,11 +29,14 @@ I2CTARGETS := $(MODULE_DIR)/i2c-core.o $(MODULE_DIR)/algo-bit.o \ # Include all dependency files INCLUDEFILES += $(I2CTARGETS:.o=.d) -all :: $(I2CTARGETS) +all-i2c: $(I2CTARGETS) +all :: all-i2c -install :: +install-i2c: $(MKDIR) $(MODDIR) install -o root -g root -m 644 $(I2CTARGETS) $(MODDIR) +install :: install-i2c -clean :: +clean-i2c: $(RM) $(I2CTARGETS) $(I2CTARGETS:.o=.d) +clean :: clean-i2c diff --git a/i2c/detect/Module.mk b/i2c/detect/Module.mk index 9b89a42e..c7136100 100644 --- a/i2c/detect/Module.mk +++ b/i2c/detect/Module.mk @@ -28,13 +28,15 @@ I2CDETECTSOURCES := $(MODULE_DIR)/detect.c # Include all dependency files INCLUDEFILES += $(I2CDETECTSOURCES:.c=.d) -all :: $(I2CDETECTTARGETS) +all-i2c-detect: $(I2CDETECTTARGETS) +all :: all-i2c-detect # No install rule -clean :: +clean-i2c-detect: $(RM) $(I2CDETECTSOURCES:.c=.d) $(I2CDETECTSOURCES:.c=.o) \ $(I2CDETECTTARGETS) +clean :: clean-i2c-detect # The targets $(MODULE_DIR)/detect: $(MODULE_DIR)/detect.o diff --git a/i2c/drivers/Module.mk b/i2c/drivers/Module.mk index 01aafa34..ecf3da10 100644 --- a/i2c/drivers/Module.mk +++ b/i2c/drivers/Module.mk @@ -27,11 +27,14 @@ I2CDRIVERTARGETS := $(MODULE_DIR)/eeprom.o # Include all dependency files INCLUDEFILES += $(I2CDRIVERTARGETS:.o=.d) -all :: $(I2CDRIVERTARGETS) +all-i2c-drivers: $(I2CDRIVERTARGETS) +all :: all-i2c-drivers -install :: +install-i2c-drivers: $(MKDIR) $(MODDIR) install -o root -g root -m 644 $(I2CDRIVERTARGETS) $(MODDIR) +install :: install-i2c-drivers -clean :: +clean-i2c-drivers: $(RM) $(I2CDRIVERTARGETS) $(I2CDRIVERTARGETS:.o=.d) +clean :: clean-i2c-drivers diff --git a/i2c/eeprom/Module.mk b/i2c/eeprom/Module.mk index ef0fb9af..bc198ec7 100644 --- a/i2c/eeprom/Module.mk +++ b/i2c/eeprom/Module.mk @@ -28,13 +28,15 @@ I2CEEPROMSOURCES := $(MODULE_DIR)/eeprom.c # Include all dependency files INCLUDEFILES += $(I2CEEPROMSOURCES:.c=.d) -all :: $(I2CEEPROMTARGETS) +all-i2c-eeprom: $(I2CEEPROMTARGETS) +all :: all-i2c-eeprom # No install rule -clean :: +clean-i2c-eeprom: $(RM) $(I2CEEPROMSOURCES:.c=.d) $(I2CEEPROMSOURCES:.c=.o) \ $(I2CEEPROMTARGETS) +clean :: clean-i2c-eeprom # The targets $(MODULE_DIR)/eeprom: $(MODULE_DIR)/eeprom.o diff --git a/i2c/i2c-core.c b/i2c/i2c-core.c index 9aaf1b6d..ebf773b7 100644 --- a/i2c/i2c-core.c +++ b/i2c/i2c-core.c @@ -17,7 +17,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* ------------------------------------------------------------------------- */ -#define RCSID "$Id: i2c-core.c,v 1.3 1998/11/19 03:07:18 frodo Exp $" +#define RCSID "$Id: i2c-core.c,v 1.4 1998/11/27 22:02:09 frodo Exp $" /* ------------------------------------------------------------------------- */ #include @@ -435,6 +435,16 @@ int i2c_probe(struct i2c_client *client, int low_addr, int hi_addr) return (i <= hi_addr) ? i : -1; } +int i2c_adapter_id(struct i2c_adapter *adap) +{ + int i; + for (i = 0; i < I2C_ADAP_MAX; i++) + if (adap == adapters[i]) + return i; + return -1; +} + + #ifdef MODULE MODULE_AUTHOR("Simon G. Vogl "); MODULE_DESCRIPTION("I2C-Bus main module"); diff --git a/i2c/i2c.h b/i2c/i2c.h index bb8a1b5a..65a33259 100644 --- a/i2c/i2c.h +++ b/i2c/i2c.h @@ -254,6 +254,10 @@ extern int i2c_probe(struct i2c_client *client, int low_addr, int hi_addr); extern int i2c_control(struct i2c_client *,unsigned int, unsigned long); +/* This call returns a unique low identifier for each registered adapter, + or -1 if the adapter was not regisitered. */ +extern int i2c_adapter_id(struct i2c_adapter *adap); + #endif /* __KERNEL__ */ diff --git a/kernel/Module.mk b/kernel/Module.mk index 454d3bac..39ce664c 100644 --- a/kernel/Module.mk +++ b/kernel/Module.mk @@ -23,16 +23,23 @@ MODULE_DIR := src # Regrettably, even 'simply expanded variables' will not put their currently # defined value verbatim into the command-list of rules... SRCTARGETS := $(MODULE_DIR)/smbus.o $(MODULE_DIR)/piix4.o $(MODULE_DIR)/isa.o \ - $(MODULE_DIR)/lm78.o $(MODULE_DIR)/i2c-proc.o + $(MODULE_DIR)/lm78.o $(MODULE_DIR)/sensors.o \ + $(MODULE_DIR)/i2c-proc.o + +HEADERFILES := $(MODULE_DIR)/sensors.h # Include all dependency files INCLUDEFILES += $(SRCTARGETS:.o=.d) -all :: $(SRCTARGETS) +all-src: $(SRCTARGETS) +all :: all-src -install :: +install-src: $(MKDIR) $(MODDIR) install -o root -g root -m 644 $(SRCTARGETS) $(MODDIR) + install -o root -g root -m 644 $(HEADERFILES) $(INCLUDEDIR) +install :: install-src -clean :: +clean-src: $(RM) $(SRCTARGETS) $(SRCTARGETS:.o=.d) +clean :: clean-src diff --git a/kernel/chips/lm78.c b/kernel/chips/lm78.c index 8a469713..6a1b7a98 100644 --- a/kernel/chips/lm78.c +++ b/kernel/chips/lm78.c @@ -1,3 +1,4 @@ + /* lm78.c - A Linux module for reading sensor data. Copyright (c) 1998 Frodo Looijaard @@ -21,15 +22,112 @@ #include #include #include +#include #include #include #include -#include "lm78.h" #include "smbus.h" #include "version.h" #include "isa.h" #include "sensors.h" +#include "i2c.h" +#include "compat.h" +/* Many LM78 constants needed 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) (0x3a + (nr)) +#define LM78_REG_FAN(nr) (0x27 + (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 + + +/* Conversions */ +static int lm78_in_conv[7] = {10000, 10000, 10000, 16892, 38000, + -34768, -15050 }; +#define IN_TO_REG(val,nr) ((((val) * 100000 / lm78_in_conv[nr]) + 8) / 16) +#define IN_FROM_REG(val,nr) (((val) * 16 * lm78_in_conv[nr]) / 100000) + +#define FAN_TO_REG(val) (((val)==0)?255:((1350000+(val))/((val)*2))) +#define FAN_FROM_REG(val) (((val)==0)?-1:\ + ((val)==255)?0:(1350000 + (val))/((val)*2)) + +#define TEMP_TO_REG(val) ((val)<0?(val)&0xff:(val)) +#define TEMP_FROM_REG(val) ((val)>0x80?(val)-0x100:(val)); + +#define VID_FROM_REG(val) ((val) == 0x0f?0:350-(val)*10) + +#define ALARMS_FROM_REG(val) (val) + +#define DIV_FROM_REG(val) (1 >> (val)) +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?1:2) + +/* Initial limits */ +#define LM78_INIT_IN_0 280 +#define LM78_INIT_IN_1 280 +#define LM78_INIT_IN_2 330 +#define LM78_INIT_IN_3 500 +#define LM78_INIT_IN_4 1200 +#define LM78_INIT_IN_5 -1200 +#define LM78_INIT_IN_6 -500 + +#define LM78_INIT_IN_PERCENTAGE 100 + +#define LM78_INIT_IN_MIN_0 \ + (LM78_INIT_IN_0 - LM78_INIT_IN_0 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_0 \ + (LM78_INIT_IN_0 + LM78_INIT_IN_0 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_1 \ + (LM78_INIT_IN_1 - LM78_INIT_IN_1 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_1 \ + (LM78_INIT_IN_1 + LM78_INIT_IN_1 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_2 \ + (LM78_INIT_IN_2 - LM78_INIT_IN_2 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_2 \ + (LM78_INIT_IN_2 + LM78_INIT_IN_2 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_3 \ + (LM78_INIT_IN_3 - LM78_INIT_IN_3 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_3 \ + (LM78_INIT_IN_3 + LM78_INIT_IN_3 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_4 \ + (LM78_INIT_IN_4 - LM78_INIT_IN_4 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_4 \ + (LM78_INIT_IN_4 + LM78_INIT_IN_4 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_5 \ + (LM78_INIT_IN_5 - LM78_INIT_IN_5 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_5 \ + (LM78_INIT_IN_5 + LM78_INIT_IN_5 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_6 \ + (LM78_INIT_IN_6 - LM78_INIT_IN_6 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_6 \ + (LM78_INIT_IN_6 + LM78_INIT_IN_6 * LM78_INIT_IN_PERCENTAGE / 100) + +#define LM78_INIT_FAN_MIN_1 3000 +#define LM78_INIT_FAN_MIN_2 3000 +#define LM78_INIT_FAN_MIN_3 3000 + +#define LM78_INIT_TEMP_OVER 60 +#define LM78_INIT_TEMP_HYST 50 #ifdef MODULE extern int init_module(void); @@ -51,6 +149,32 @@ extern int cleanup_module(void); 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 semaphore lock; + int sysctl_id; + + 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 */ + u8 temp; /* Register value */ + u8 temp_over; /* Register value */ + u8 temp_hyst; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u8 vid; /* Register encoding, combined */ + u16 alarms; /* Register encoding, combined */ +}; + + static int lm78_init(void); static int lm78_cleanup(void); @@ -62,21 +186,44 @@ static int lm78_detach_isa(struct isa_client *client); static int lm78_detach_smbus(struct i2c_client *client); static int lm78_new_client(struct i2c_adapter *adapter, struct i2c_client *new_client); -static void lm78_init_client(struct i2c_client *client); static void lm78_remove_client(struct i2c_client *client); static int lm78_command(struct i2c_client *client, unsigned int cmd, void *arg); static void lm78_inc_use (struct i2c_client *client); static void lm78_dec_use (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 void lm78_update_client(struct i2c_client *client); +static void lm78_init_client(struct i2c_client *client); + +static int lm78_sysctl (ctl_table *table, int *name, int nlen, void *oldval, + size_t *oldlenp, void *newval, size_t newlen, + void **context); +static int lm78_proc (ctl_table *ctl, int write, struct file * filp, + void *buffer, size_t *lenp); + + +static void write_in(struct i2c_client *client, int nr, int nrels, + long *results); +static void read_in(struct i2c_client *client, int nr, long *results); +static void write_fan(struct i2c_client *client, int nr, int nrels, + long *results); +static void read_fan(struct i2c_client *client, int nr, long *results); +static void write_temp(struct i2c_client *client, int nrels, long *results); +static void read_temp(struct i2c_client *client, long *results); +static void read_vid(struct i2c_client *client, long *results); +static void read_alarms(struct i2c_client *client, long *results); +static void write_fan_div(struct i2c_client *client, int nrels, long *results); +static void read_fan_div(struct i2c_client *client, long *results); + + /* I choose here for semi-static LM78 allocation. Complete dynamic allocation could also be used; the code needed for this would probably - take more memory than the datastructures take now */ + take more memory than the datastructure takes now. */ #define MAX_LM78_NR 4 static struct i2c_client *lm78_list[MAX_LM78_NR]; -static struct semaphore lm78_semaphores[MAX_LM78_NR]; /* The driver. I choose to use type i2c_driver, as at is identical to both smbus_driver and isa_driver, and clients could be of either kind */ @@ -94,8 +241,34 @@ static struct i2c_driver lm78_driver = { /* Used by lm78_init/cleanup */ static int lm78_initialized = 0; +/* The /proc/sys entries */ +/* These files are created for each detected LM78. 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 lm78_dir_table_template[] = { + { LM78_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_FAN2, "fan1", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_FAN3, "fan1", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_VID, "vid", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + + { LM78_SYSCTL_ALARMS, "alarms", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl } +}; + + /* This function is called when: - * lm78_driver is inserted, for each available adapter + * 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) */ int lm78_attach_adapter(struct i2c_adapter *adapter) { @@ -105,7 +278,10 @@ int lm78_attach_adapter(struct i2c_adapter *adapter) return lm78_detect_smbus(adapter); } -/* This function is called whenever a client should be removed */ +/* This function is called whenever a client should be removed: + * lm78_driver is removed (when this module is unloaded) + * when an adapter is removed which has a lm78 client (and lm78_driver + is still present). */ int lm78_detach_client(struct i2c_client *client) { if (i2c_is_isa_client(client)) @@ -138,42 +314,74 @@ int lm78_detect_isa(struct isa_adapter *adapter) request_region(address, LM78_EXTENT, "lm78"); - if (! (new_client = kmalloc(sizeof(struct isa_client), GFP_KERNEL))) + /* Allocate space for a new client structure */ + if (! (new_client = kmalloc(sizeof(struct isa_client) + + sizeof(struct lm78_data), + GFP_KERNEL))) { - release_region(address,LM78_EXTENT); err=-ENOMEM; - continue; - } + goto ERROR1; + } + + /* Fill the new client structure with data */ + new_client->data = (struct lm78_data *) (new_client + 1); new_client->addr = 0; new_client->isa_addr = address; if ((err = lm78_new_client((struct i2c_adapter *) adapter, - (struct i2c_client *) new_client))) { - release_region(address, LM78_EXTENT); - kfree(new_client); - continue; - } - if ((err = isa_attach_client(new_client))) { - release_region(address, LM78_EXTENT); - lm78_remove_client((struct i2c_client *) new_client); - kfree(new_client); - continue; - } + (struct i2c_client *) new_client))) + goto ERROR2; + + /* Tell i2c-core a new client has arrived */ + if ((err = isa_attach_client(new_client))) + goto ERROR3; + + /* Register a new directory entry with module sensors */ + if ((err = sensors_register_entry((struct i2c_client *) new_client,"lm78", + lm78_dir_table_template) < 0)) + goto ERROR4; + ((struct lm78_data *) (new_client->data)) -> sysctl_id = err; + + /* Initialize the LM78 chip */ lm78_init_client((struct i2c_client *) new_client); + continue; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + +ERROR4: + isa_detach_client(new_client); +ERROR3: + lm78_remove_client((struct i2c_client *) new_client); +ERROR2: + kfree(new_client); +ERROR1: + release_region(address, LM78_EXTENT); } return err; + } /* Deregister and remove a LM78 client */ int lm78_detach_isa(struct isa_client *client) { - int err; + int err,i; + for (i = 0; i < MAX_LM78_NR; i++) + if ((client == (struct isa_client *) (lm78_list[i]))) + break; + if (i == MAX_LM78_NR) { + printk("lm78.o: Client to detach not found.\n"); + return -ENOENT; + } + + sensors_deregister_entry(((struct lm78_data *)(client->data))->sysctl_id); + if ((err = isa_detach_client(client))) { printk("lm78.o: Client deregistration failed, client not detached.\n"); return err; } - release_region(client->isa_addr,LM78_EXTENT); lm78_remove_client((struct i2c_client *) client); kfree(client); + release_region(client->isa_addr,LM78_EXTENT); return 0; } @@ -194,25 +402,59 @@ int lm78_detect_smbus(struct i2c_adapter *adapter) /* Real detection code goes here */ - new_client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + /* Allocate space for a new client structure */ + if (! (new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct lm78_data), + GFP_KERNEL))) { + err = -ENOMEM; + continue; + } + + /* Fill the new client structure with data */ + new_client->data = (struct lm78_data *) (new_client + 1); new_client->addr = address; - if ((err = lm78_new_client(adapter,new_client))) { - kfree(new_client); - continue; - } - if ((err = i2c_attach_client(new_client))) { - lm78_remove_client(new_client); - kfree(new_client); - continue; - } + if ((err = lm78_new_client(adapter,new_client))) + goto ERROR2; + + /* Tell i2c-core a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto ERROR3; + + /* Register a new directory entry with module sensors */ + if ((err = sensors_register_entry(new_client,"lm78", + lm78_dir_table_template) < 0)) + goto ERROR4; + ((struct lm78_data *) (new_client->data))->sysctl_id = err; + + /* Initialize the LM78 chip */ lm78_init_client(new_client); + continue; + +/* 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: + lm78_remove_client((struct i2c_client *) new_client); +ERROR2: + kfree(new_client); } return err; } int lm78_detach_smbus(struct i2c_client *client) { - int err; + int err,i; + for (i = 0; i < MAX_LM78_NR; i++) + if (client == lm78_list[i]) + break; + if ((i == MAX_LM78_NR)) { + printk("lm78.o: Client to detach not found.\n"); + return -ENOENT; + } + + sensors_deregister_entry(((struct lm78_data *)(client->data))->sysctl_id); + if ((err = i2c_detach_client(client))) { printk("lm78.o: Client deregistration failed, client not detached.\n"); return err; @@ -228,6 +470,7 @@ int lm78_new_client(struct i2c_adapter *adapter, struct i2c_client *new_client) { int i; + struct lm78_data *data; /* First, seek out an empty slot */ for(i = 0; i < MAX_LM78_NR; i++) @@ -240,28 +483,26 @@ int lm78_new_client(struct i2c_adapter *adapter, } lm78_list[i] = new_client; - lm78_semaphores[i] = MUTEX; - new_client->data = &lm78_semaphores[i]; strcpy(new_client->name,"LM78 chip"); new_client->id = i; new_client->adapter = adapter; new_client->driver = &lm78_driver; + data = new_client->data; + data->valid = 0; + data->lock = MUTEX; + data->update_lock = MUTEX; return 0; } +/* Inverse of lm78_new_client */ void lm78_remove_client(struct i2c_client *client) { int i; for (i = 0; i < MAX_LM78_NR; i++) - if (client == lm78_list[i]) + if (client == lm78_list[i]) lm78_list[i] = NULL; } -/* Called when we have found a new LM78. It should set limits, etc. */ -void lm78_init_client(struct i2c_client *client) -{ -} - /* No commands defined yet */ int lm78_command(struct i2c_client *client, unsigned int cmd, void *arg) { @@ -316,37 +557,346 @@ int lm78_write_value(struct i2c_client *client, u8 reg, u8 value) return smbus_write_byte_data(client->adapter, client->addr, reg,value); } -/* ANYTHING BELOW IS JUST AN EXAMPLE. IGNORE IF YOU WANT TO BASE A DRIVER - ON THE CODE IN THIS FILE */ - -/* Stupid entry in /proc */ -static int proc_function(char *buf, char **start, off_t offset, int len, - int unused) +/* Called when we have found a new LM78. It should set limits, etc. */ +void lm78_init_client(struct i2c_client *client) { - int i; - len = 0; - for (i = 0; i < MAX_LM78_NR; i++) - if(lm78_list[i]) { - if(i2c_is_isa_client(lm78_list[i])) - len += sprintf(buf+len,"(isa) %d: address=%x\n",i, - ((struct isa_client *) (lm78_list[i]))->isa_addr); - else - len += sprintf(buf+len,"(i2c) %d: address=%x\n",i, - lm78_list[i]->addr); - } - return len; + /* Reset all except Watchdog values and last conversion values + This sets fan-divs to 2, among others */ + lm78_write_value(client,LM78_REG_CONFIG,0x80); + + lm78_write_value(client,LM78_REG_IN_MIN(0),IN_TO_REG(LM78_INIT_IN_MIN_0,0)); + lm78_write_value(client,LM78_REG_IN_MAX(0),IN_TO_REG(LM78_INIT_IN_MAX_0,0)); + lm78_write_value(client,LM78_REG_IN_MIN(1),IN_TO_REG(LM78_INIT_IN_MIN_1,1)); + lm78_write_value(client,LM78_REG_IN_MAX(1),IN_TO_REG(LM78_INIT_IN_MAX_1,1)); + lm78_write_value(client,LM78_REG_IN_MIN(2),IN_TO_REG(LM78_INIT_IN_MIN_2,2)); + lm78_write_value(client,LM78_REG_IN_MAX(2),IN_TO_REG(LM78_INIT_IN_MAX_2,2)); + lm78_write_value(client,LM78_REG_IN_MIN(3),IN_TO_REG(LM78_INIT_IN_MIN_3,3)); + lm78_write_value(client,LM78_REG_IN_MAX(3),IN_TO_REG(LM78_INIT_IN_MAX_3,3)); + lm78_write_value(client,LM78_REG_IN_MIN(4),IN_TO_REG(LM78_INIT_IN_MIN_4,4)); + lm78_write_value(client,LM78_REG_IN_MAX(4),IN_TO_REG(LM78_INIT_IN_MAX_4,4)); + lm78_write_value(client,LM78_REG_IN_MIN(5),IN_TO_REG(LM78_INIT_IN_MIN_5,5)); + lm78_write_value(client,LM78_REG_IN_MAX(5),IN_TO_REG(LM78_INIT_IN_MAX_5,5)); + lm78_write_value(client,LM78_REG_IN_MIN(6),IN_TO_REG(LM78_INIT_IN_MIN_6,6)); + lm78_write_value(client,LM78_REG_IN_MAX(6),IN_TO_REG(LM78_INIT_IN_MAX_6,6)); + lm78_write_value(client,LM78_REG_FAN_MIN(1),FAN_TO_REG(LM78_INIT_FAN_MIN_1)); + lm78_write_value(client,LM78_REG_FAN_MIN(2),FAN_TO_REG(LM78_INIT_FAN_MIN_2)); + lm78_write_value(client,LM78_REG_FAN_MIN(3),FAN_TO_REG(LM78_INIT_FAN_MIN_3)); + lm78_write_value(client,LM78_REG_TEMP_OVER,TEMP_TO_REG(LM78_INIT_TEMP_OVER)); + lm78_write_value(client,LM78_REG_TEMP_HYST,TEMP_TO_REG(LM78_INIT_TEMP_HYST)); + + /* Start monitoring */ + lm78_write_value(client,LM78_REG_CONFIG, + (lm78_read_value(client,LM78_REG_CONFIG) & 0xf7) | 0x01); + } -/* OK, this is a test entry. Just ignore, it is not important. */ -static struct proc_dir_entry proc_entry = - { - 0,12,"sensors-test", - S_IFREG | S_IRUGO, 1, 0, 0, - 0, NULL, - &proc_function - }; +void lm78_update_client(struct i2c_client *client) +{ + struct lm78_data *data = client->data; + int i; + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ+HZ/2 ) || + (jiffies < data->last_updated) || ! data->valid) { + + 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 = 1; i <= 3; i++) { + data->fan[i-1] = lm78_read_value(client,LM78_REG_FAN(i)); + data->fan_min[i-1] = 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; + 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; + } + + up(&data->update_lock); +} + +/* This function is called when /proc/sys/dev/lm78-???/... is accessed */ +int lm78_proc (ctl_table *ctl, int write, struct file * filp, + void *buffer, size_t *lenp) +{ + int nrels,mag; + long results[7]; + struct i2c_client *client = ctl -> extra1; + + /* If buffer is size 0, or we try to read when not at the start, we + return nothing. Note that I think writing when not at the start + does not work either, but anyway, this is straight from the kernel + sources. */ + if (!*lenp || (filp->f_pos && !write)) { + *lenp = 0; + return 0; + } + + /* How many numbers are found within these files, and how to scale them? */ + switch (ctl->ctl_name) { + case LM78_SYSCTL_IN0: case LM78_SYSCTL_IN1: case LM78_SYSCTL_IN2: + case LM78_SYSCTL_IN3: case LM78_SYSCTL_IN4: case LM78_SYSCTL_IN5: + case LM78_SYSCTL_IN6: + nrels=3; + mag=2; + break; + case LM78_SYSCTL_FAN_DIV: case LM78_SYSCTL_TEMP: + nrels=3; + mag=0; + break; + case LM78_SYSCTL_FAN1: case LM78_SYSCTL_FAN2: case LM78_SYSCTL_FAN3: + nrels=2; + mag=0; + break; + case LM78_SYSCTL_VID: + nrels=1; + mag=2; + break; + case LM78_SYSCTL_ALARMS: + nrels=1; + mag=0; + break; + default: /* Should never be called */ + return -EINVAL; + } + + /* OK, try writing stuff. */ + if (write) { + sensors_parse_reals(&nrels,buffer,*lenp,results,mag); + if (nrels == 0) + return 0; + switch (ctl->ctl_name) { + case LM78_SYSCTL_IN0: write_in(client,0,nrels,results); break; + case LM78_SYSCTL_IN1: write_in(client,1,nrels,results); break; + case LM78_SYSCTL_IN2: write_in(client,2,nrels,results); break; + case LM78_SYSCTL_IN3: write_in(client,3,nrels,results); break; + case LM78_SYSCTL_IN4: write_in(client,4,nrels,results); break; + case LM78_SYSCTL_IN5: write_in(client,5,nrels,results); break; + case LM78_SYSCTL_IN6: write_in(client,6,nrels,results); break; + case LM78_SYSCTL_FAN1: write_fan(client,1,nrels,results); break; + case LM78_SYSCTL_FAN2: write_fan(client,2,nrels,results); break; + case LM78_SYSCTL_FAN3: write_fan(client,3,nrels,results); break; + case LM78_SYSCTL_FAN_DIV: write_fan_div(client,nrels,results);break; + case LM78_SYSCTL_TEMP: write_temp(client,nrels,results);break; + case LM78_SYSCTL_VID: case LM78_SYSCTL_ALARMS: break; + default: /* Should never be called */ *lenp=0; return -EINVAL; break; + } + filp->f_pos += *lenp; + return 0; + } else { /* read */ + /* Update all values in LM_Sensor_Data */ + + lm78_update_client((struct i2c_client *) (ctl->extra1)); + + /* Read the values to print into results */ + switch (ctl->ctl_name) { + case LM78_SYSCTL_IN0: read_in(client,0,results);break; + case LM78_SYSCTL_IN1: read_in(client,1,results);break; + case LM78_SYSCTL_IN2: read_in(client,2,results);break; + case LM78_SYSCTL_IN3: read_in(client,3,results);break; + case LM78_SYSCTL_IN4: read_in(client,4,results);break; + case LM78_SYSCTL_IN5: read_in(client,5,results);break; + case LM78_SYSCTL_IN6: read_in(client,6,results);break; + case LM78_SYSCTL_FAN1: read_fan(client,1,results);break; + case LM78_SYSCTL_FAN2: read_fan(client,2,results);break; + case LM78_SYSCTL_FAN3: read_fan(client,3,results);break; + case LM78_SYSCTL_TEMP: read_temp(client,results);break; + case LM78_SYSCTL_FAN_DIV: read_fan_div(client,results);break; + case LM78_SYSCTL_VID: read_vid(client,results);break; + case LM78_SYSCTL_ALARMS: read_alarms(client,results);break; + default: /* Should never be called */ return -EINVAL; + } + /* OK, print it now */ + sensors_write_reals(nrels,buffer,lenp,results,mag); + filp->f_pos += *lenp; + return 0; + } +} + +/* This function is called when a sysctl on a lm78 file is done */ +int lm78_sysctl (ctl_table *table, int *name, int nlen, void *oldval, + size_t *oldlenp, void *newval, size_t newlen, + void **context) +{ + long results[7]; + int nrels,oldlen; + struct i2c_client *client = table -> extra1; + + /* How many numbers are found within these files, and how to scale them? */ + switch (table->ctl_name) { + case LM78_SYSCTL_IN0: case LM78_SYSCTL_IN1: case LM78_SYSCTL_IN2: + case LM78_SYSCTL_IN3: case LM78_SYSCTL_IN4: case LM78_SYSCTL_IN5: + case LM78_SYSCTL_IN6: case LM78_SYSCTL_TEMP: case LM78_SYSCTL_FAN_DIV: + nrels=3; + break; + case LM78_SYSCTL_FAN1: case LM78_SYSCTL_FAN2: case LM78_SYSCTL_FAN3: + nrels=2; + break; + case LM78_SYSCTL_VID: case LM78_SYSCTL_ALARMS: + nrels=1; + break; + default: /* Should never be called */ + return -EINVAL; + } + + /* Check if we need to output the old values */ + if (oldval && oldlenp && ! get_user_data(oldlen,oldlenp) && oldlen) { + + /* Update all values in LM_Sensor_Data */ + lm78_update_client((struct i2c_client *) (table->extra1)); + switch (table->ctl_name) { + case LM78_SYSCTL_IN0: read_in(client,0,results);break; + case LM78_SYSCTL_IN1: read_in(client,1,results);break; + case LM78_SYSCTL_IN2: read_in(client,2,results);break; + case LM78_SYSCTL_IN3: read_in(client,3,results);break; + case LM78_SYSCTL_IN4: read_in(client,4,results);break; + case LM78_SYSCTL_IN5: read_in(client,5,results);break; + case LM78_SYSCTL_IN6: read_in(client,6,results);break; + case LM78_SYSCTL_FAN1: read_fan(client,1,results);break; + case LM78_SYSCTL_FAN2: read_fan(client,2,results);break; + case LM78_SYSCTL_FAN3: read_fan(client,3,results);break; + case LM78_SYSCTL_TEMP: read_temp(client,results);break; + case LM78_SYSCTL_FAN_DIV: read_fan_div(client,results);break; + case LM78_SYSCTL_VID: read_vid(client,results);break; + case LM78_SYSCTL_ALARMS: read_alarms(client,results);break; + default: /* Should never be called */ return -EINVAL; + } + /* Note the rounding factor! */ + if (nrels * sizeof(long) < oldlen) + oldlen = nrels * sizeof(long); + oldlen = (oldlen / sizeof(long)) * sizeof(long); + copy_to_user(oldval,results,oldlen); + put_user(oldlen,oldlenp); + } + + /* Check to see whether we need to read the new values */ + if (newval && newlen) { + if (nrels * sizeof(long) < newlen) + newlen = nrels * sizeof(long); + nrels = newlen / sizeof(long); + newlen = (newlen / sizeof(long)) * sizeof(long); + copy_from_user(results,newval,newlen); + + switch (table->ctl_name) { + case LM78_SYSCTL_IN0: write_in(client,0,nrels,results); break; + case LM78_SYSCTL_IN1: write_in(client,1,nrels,results); break; + case LM78_SYSCTL_IN2: write_in(client,2,nrels,results); break; + case LM78_SYSCTL_IN3: write_in(client,3,nrels,results); break; + case LM78_SYSCTL_IN4: write_in(client,4,nrels,results); break; + case LM78_SYSCTL_IN5: write_in(client,5,nrels,results); break; + case LM78_SYSCTL_IN6: write_in(client,6,nrels,results); break; + case LM78_SYSCTL_FAN1: write_fan(client,1,nrels,results); break; + case LM78_SYSCTL_FAN2: write_fan(client,2,nrels,results); break; + case LM78_SYSCTL_FAN3: write_fan(client,3,nrels,results); break; + case LM78_SYSCTL_TEMP: write_temp(client,nrels,results); break; + case LM78_SYSCTL_FAN_DIV: write_fan_div(client,nrels,results);break; + case LM78_SYSCTL_VID: case LM78_SYSCTL_ALARMS: break; + default: /* Should never be called */ return -EINVAL; break; + } + } + return 1; /* We have done all the work */ +} + +void write_in(struct i2c_client *client, int nr, int nrels, long *results) +{ + struct lm78_data *data = client->data; + if (nrels >= 1) { + data->in_min[nr] = IN_TO_REG(results[0],nr); + lm78_write_value(client,LM78_REG_IN_MIN(nr),data->in_min[nr]); + } + if (nrels >= 2) { + data->in_max[nr] = IN_TO_REG(results[1],nr); + lm78_write_value(client,LM78_REG_IN_MAX(nr),data->in_max[nr]); + } +} + +void read_in(struct i2c_client *client, int nr, long *results) +{ + struct lm78_data *data = client->data; + results[0] = IN_FROM_REG(data->in_min[nr],nr); + results[1] = IN_FROM_REG(data->in_min[nr],nr); + results[2] = IN_FROM_REG(data->in_min[nr],nr); +} + +void write_fan(struct i2c_client *client, int nr, int nrels, long *results) +{ + struct lm78_data *data = client->data; + if (nrels >= 1) { + data->fan_min[nr-1] = FAN_TO_REG(results[0]); + lm78_write_value(client,LM78_REG_FAN_MIN(nr),data->fan_min[nr-1]); + } +} + +void read_fan(struct i2c_client *client, int nr, long *results) +{ + struct lm78_data *data = client->data; + results[0] = FAN_FROM_REG(data->fan_min[nr-1]); + results[1] = FAN_FROM_REG(data->fan[nr-1]); +} + +void write_temp(struct i2c_client *client, int nrels, long *results) +{ + struct lm78_data *data = client->data; + if (nrels >= 1) { + data->temp_over = TEMP_TO_REG(results[0]); + lm78_write_value(client,LM78_REG_TEMP_OVER,data->temp_over); + } + if (nrels >= 2) { + data->temp_hyst = TEMP_TO_REG(results[0]); + lm78_write_value(client,LM78_REG_TEMP_HYST,data->temp_hyst); + } +} + +void read_temp(struct i2c_client *client, long *results) +{ + struct lm78_data *data = client->data; + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); +} + +void read_vid(struct i2c_client *client, long *results) +{ + struct lm78_data *data = client->data; + results[0] = VID_FROM_REG(data->vid); +} + +void read_alarms(struct i2c_client *client, long *results) +{ + struct lm78_data *data = client->data; + results[0] = ALARMS_FROM_REG(data->alarms); +} + +void read_fan_div(struct i2c_client *client, long *results) +{ + struct lm78_data *data = client->data; + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); +} + +void write_fan_div(struct i2c_client *client, int nrels, long *results) +{ + struct lm78_data *data = client->data; + if (nrels >= 2) + data->fan_div[1] = DIV_TO_REG(results[1]); + if (nrels >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + lm78_write_value(client,LM78_REG_VID_FANDIV, + (data->fan_div[0] >> 4) | (data->fan_div[1] >> 6)); + } +} + int lm78_init(void) { int res; @@ -354,16 +904,6 @@ int lm78_init(void) printk("lm78.o version %s (%s)\n",LM_VERSION,LM_DATE); lm78_initialized = 0; - /* OK, we register some stupid proc file here. This is *just* *temporary*, - for test purposes. Ignore if you want. Only works for kernels 2.0.x. */ - if ((res = proc_register_dynamic(&proc_root,&proc_entry))) { - printk("lm78.o: Couldn't create /proc/sensors-test, " - "module not inserted.\n"); - lm78_cleanup(); - return res; - } - lm78_initialized ++; - if ((res =i2c_add_driver(&lm78_driver))) { printk("lm78.o: Driver registration failed, module not inserted.\n"); lm78_cleanup(); @@ -377,7 +917,7 @@ int lm78_cleanup(void) { int res; - if (lm78_initialized >= 2) { + if (lm78_initialized >= 1) { if ((res = i2c_del_driver(&lm78_driver))) { printk("lm78.o: Driver deregistration failed, module not removed.\n"); return res; @@ -385,14 +925,6 @@ int lm78_cleanup(void) } else lm78_initialized --; - if (lm78_initialized >= 1) { - if((res = proc_unregister(&proc_root,proc_entry.low_ino))) { - printk("lm78.o: Deregistration of /proc/sensors_test failed, " - "module not removed.\n"); - return res; - } - } else - lm78_initialized --; return 0; } diff --git a/kernel/compat.h b/kernel/compat.h index c6b5049e..e3adedba 100644 --- a/kernel/compat.h +++ b/kernel/compat.h @@ -37,4 +37,20 @@ #endif #endif /* def MODULE */ +/* copy_from/to_usr is called memcpy_from/to_fs in 2.0 kernels; perhaps in + some early 2.1 kernels too? */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,1,4)) +#define copy_from_user memcpy_fromfs +#define copy_to_user memcpy_tofs +#endif + +/* get_user was redefined in 2.1 kernels to use two arguments, and returns + an error code */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,1,4)) +#define get_user_data(to,from) ((to) = get_user(from),0) +#else +#define get_user_data(to,from) get_user(to,from) +#endif + + #endif /* SENSORS_COMPAT_H */ diff --git a/kernel/include/compat.h b/kernel/include/compat.h index c6b5049e..e3adedba 100644 --- a/kernel/include/compat.h +++ b/kernel/include/compat.h @@ -37,4 +37,20 @@ #endif #endif /* def MODULE */ +/* copy_from/to_usr is called memcpy_from/to_fs in 2.0 kernels; perhaps in + some early 2.1 kernels too? */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,1,4)) +#define copy_from_user memcpy_fromfs +#define copy_to_user memcpy_tofs +#endif + +/* get_user was redefined in 2.1 kernels to use two arguments, and returns + an error code */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,1,4)) +#define get_user_data(to,from) ((to) = get_user(from),0) +#else +#define get_user_data(to,from) get_user(to,from) +#endif + + #endif /* SENSORS_COMPAT_H */ diff --git a/kernel/include/sensors.h b/kernel/include/sensors.h index 0fe6f832..30fa627a 100644 --- a/kernel/include/sensors.h +++ b/kernel/include/sensors.h @@ -20,8 +20,45 @@ #ifndef SENSORS_SENSORS_H #define SENSORS_SENSORS_H +#include + +#ifdef __KERNEL__ +extern int sensors_register_entry(struct i2c_client *client, + const char *prefix, ctl_table *ctl_template); +extern void sensors_deregister_entry(int id); +extern void sensors_parse_reals(int *nrels, void *buffer, int bufsize, + long *results, int magnitude); +extern void sensors_write_reals(int nrels,void *buffer,int *bufsize, + long *results, int magnitude); + +#endif /* def __KERNEL__ */ + + /* Driver IDs */ #define I2C_DRIVERID_I2CPROC 1001 #define I2C_DRIVERID_LM78 1002 +/* Sysctl IDs */ +#ifdef DEV_HWMON +#define DEV_SENSORS DEV_HWMON +#else /* ndef DEV_HWMOM */ +#define DEV_SENSORS 2 /* The id of the lm_sensors directory within the + dev table */ +#endif /* def DEV_HWMON */ + +#define LM78_SYSCTL_IN0 1000 +#define LM78_SYSCTL_IN1 1001 +#define LM78_SYSCTL_IN2 1002 +#define LM78_SYSCTL_IN3 1003 +#define LM78_SYSCTL_IN4 1004 +#define LM78_SYSCTL_IN5 1005 +#define LM78_SYSCTL_IN6 1006 +#define LM78_SYSCTL_FAN1 1101 +#define LM78_SYSCTL_FAN2 1102 +#define LM78_SYSCTL_FAN3 1103 +#define LM78_SYSCTL_TEMP 1200 +#define LM78_SYSCTL_VID 1300 +#define LM78_SYSCTL_FAN_DIV 2000 +#define LM78_SYSCTL_ALARMS 2001 + #endif /* def SENSORS_SENSORS_H */ diff --git a/kernel/sensors.c b/kernel/sensors.c new file mode 100644 index 00000000..322da300 --- /dev/null +++ b/kernel/sensors.c @@ -0,0 +1,346 @@ +/* + sensors.c - A Linux module for reading sensor data. + Copyright (c) 1998 Frodo Looijaard + + 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 + +#include "version.h" +#include "compat.h" +#include "i2c.h" +#include "isa.h" +#include "sensors.h" + +#ifdef MODULE +extern int init_module(void); +extern int cleanup_module(void); +#endif /* MODULE */ + +int sensors_register_entry(struct i2c_client *client ,const char *prefix, + ctl_table *ctl_template); +static int sensors_create_name(char **name, const char *prefix, + struct i2c_adapter * adapter, int addr); +static int sensors_init(void); +static int sensors_cleanup(void); + +#define SENSORS_ENTRY_MAX 20 +static struct ctl_table_header *sensors_entries[SENSORS_ENTRY_MAX]; + +static ctl_table sysctl_table[] = { + { CTL_DEV, "dev", NULL, 0, 0555 }, + { 0 }, + { DEV_SENSORS, "sensors", NULL, 0, 0555 }, + { 0 }, + { 0, NULL, NULL, 0, 0555 }, + { 0 } +}; + +/* This returns a nice name for a new directory; for example lm78-isa-0310 + (for a LM78 chip on the ISA bus at port 0x310), or lm75-i2c-3-4e (for + a LM75 chip on the third i2c bus at address 0x4e). + name is allocated first. */ +int sensors_create_name(char **name, const char *prefix, + struct i2c_adapter * adapter, int addr) +{ + char name_buffer[50]; + int id; + if (i2c_is_isa_adapter(adapter)) + sprintf(name_buffer,"%s-isa-%d",prefix,addr); + else { + if ((id = i2c_adapter_id(adapter)) < 0); + return -ENOENT; + sprintf(name_buffer,"%s-i2c-%d-%d",prefix,id,addr); + } + *name = kmalloc(strlen(name_buffer)+1,GFP_KERNEL); + strcpy(*name,name_buffer); + return 0; +} + +/* This rather complex function must be called when you want to add an entry + to /proc/sys/dev/sensors/chips (not yet implemented). It also creates + a new directory within /proc/sys/dev/sensors/. + ctl_template should be a template of the newly created directory. It is + copied in memory. The extra1 field of each file is set to point to client. + If any driver wants subdirectories within the newly created directory, + this function must be updated! */ +int sensors_register_entry(struct i2c_client *client ,const char *prefix, + ctl_table *ctl_template) +{ + int i,res,len,id; + ctl_table *new_table; + char *name; + struct ctl_table_header *new_header; + + if ((res = sensors_create_name(&name,prefix,client->adapter, + i2c_is_isa_client(client)? + ((struct isa_client *) client)->isa_addr: + client->addr))) + return res; + + for (id = 0; id < SENSORS_ENTRY_MAX; i++) + if (! sensors_entries[id]) { + break; + } + if (id == SENSORS_ENTRY_MAX) { + kfree(name); + return -ENOMEM; + } + id += 256; + + len = 0; + while (ctl_template[len].procname) + len++; + len += 7; + if (! (new_table = kmalloc(sizeof(ctl_table) * len,GFP_KERNEL))) { + kfree(name); + return -ENOMEM; + } + + memcpy(new_table,sysctl_table,6 * sizeof(ctl_table)); + new_table[0].child = &new_table[2]; + new_table[2].child = &new_table[4]; + new_table[4].child = &new_table[6]; + new_table[4].procname = name; + new_table[4].ctl_name = id; + memcpy(new_table+6,ctl_template,(len-6) * sizeof(ctl_table)); + for (i = 6; i < len; i++) + new_table[i].extra1 = client; + + if (! (new_header = register_sysctl_table(new_table,0))) { + kfree(new_table); + kfree(name); + return -ENOMEM; + } + + sensors_entries[i] = new_header; + + return id; +} + +void sensors_deregister_entry(int id) +{ + ctl_table *table; + id -= 256; + if (sensors_entries[id]) { + table = sensors_entries[id]->ctl_table; + unregister_sysctl_table(sensors_entries[id]); + kfree((void *) (table->procname)); + kfree(table); + sensors_entries[id] = 0; + } +} + +/* nrels contains initially the maximum number of elements which can be + put in results, and finally the number of elements actually put there. + A magnitude of 1 will multiply everything with 10; etc. + buffer, bufsize is the character buffer we read from and its length. + results will finally contain the parsed integers. + + Buffer should contain several reals, separated by whitespace. A real + has the following syntax: + [ Minus ] Digit* [ Dot Digit* ] + (everything between [] is optional; * means zero or more). + When the next character is unparsable, everything is skipped until the + next whitespace. + + WARNING! This is tricky code. I have tested it, but there may still be + hidden bugs in it, even leading to crashes and things! +*/ +void sensors_parse_reals(int *nrels, void *buffer, int bufsize, + long *results, int magnitude) +{ + int maxels,min,mag; + long res; + char nextchar=0; + + maxels = *nrels; + *nrels = 0; + + while (bufsize && (*nrels < maxels)) { + + /* Skip spaces at the start */ + while (bufsize && ! get_user_data(nextchar,(char *) buffer) && + isspace((int) nextchar)) { + bufsize --; + ((char *) buffer)++; + } + + /* Well, we may be done now */ + if (! bufsize) + return; + + /* New defaults for our result */ + min = 0; + res = 0; + mag = magnitude; + + /* Check for a minus */ + if (! get_user_data(nextchar,(char *) buffer) && (nextchar == '-')) { + min=1; + bufsize--; + ((char *) buffer)++; + } + + /* Digits before a decimal dot */ + while (bufsize && !get_user_data(nextchar,(char *) buffer) && + isdigit((int) nextchar)) { + res = res * 10 + nextchar - '0'; + bufsize--; + ((char *) buffer)++; + } + + /* If mag < 0, we must actually divide here! */ + while (mag < 0) { + res = res / 10; + mag++; + } + + if (bufsize && (nextchar == '.')) { + /* Skip the dot */ + bufsize--; + ((char *) buffer)++; + + /* Read digits while they are significant */ + while(bufsize && (mag > 0) && + !get_user_data(nextchar,(char *) buffer) && + isdigit((int) nextchar)) { + res = res * 10 + nextchar - '0'; + mag--; + bufsize--; + ((char *) buffer)++; + } + } + /* If we are out of data, but mag > 0, we need to scale here */ + while (mag > 0) { + res = res * 10; + mag --; + } + + /* Skip everything until we hit whitespace */ + while(bufsize && !get_user_data(nextchar,(char *) buffer) && + isspace ((int) nextchar)) { + bufsize --; + ((char *) buffer) ++; + } + + /* Put res in results */ + results[*nrels] = (min?-1:1)*res; + (*nrels)++; + } + + /* Well, there may be more in the buffer, but we need no more data. + Ignore anything that is left. */ + return; +} + +void sensors_write_reals(int nrels,void *buffer,int *bufsize,long *results, + int magnitude) +{ + #define BUFLEN 20 + char BUF[BUFLEN+1]; /* An individual representation should fit in here! */ + char printfstr[10]; + int nr=0; + int buflen,mag,times; + int curbufsize=0; + + while ((nr < nrels) && (curbufsize < *bufsize)) { + mag=magnitude; + + if (nr != 0) { + put_user(' ', (char *) buffer); + curbufsize ++; + ((char *) buffer) ++; + } + + /* Fill BUF with the representation of the next string */ + if (mag <= 0) { + + buflen=sprintf(BUF,"%ld",results[nr]); + if (buflen < 0) { /* Oops, a sprintf error! */ + *bufsize=0; + return; + } + while ((mag < 0) && (buflen < BUFLEN)) { + BUF[buflen++]='0'; + mag++; + } + BUF[buflen]=0; + } else { + times=1; + for (times=1; mag-- > 0; times *= 10); + if (results[nr] < 0) { + BUF[0] = '-'; + buflen = 1; + } else + buflen=0; + strcpy(printfstr,"%ld.%0Xld"); + printfstr[6]=magnitude+'0'; + buflen+=sprintf(BUF+buflen,printfstr,abs(results[nr])/times, + abs(results[nr])%times); + if (buflen < 0) { /* Oops, a sprintf error! */ + *bufsize=0; + return; + } + } + + /* Now copy it to the user-space buffer */ + if (buflen + curbufsize > *bufsize) + buflen=*bufsize-curbufsize; + copy_to_user(buffer,BUF,buflen); + curbufsize += buflen; + (char *) buffer += buflen; + + nr ++; + } + if (curbufsize < *bufsize) { + put_user('\n', (char *) buffer); + curbufsize ++; + } + *bufsize=curbufsize; +} + +int sensors_init(void) +{ + printk("sensors.o version %s (%s)\n",LM_VERSION,LM_DATE); + return 0; +} + +int sensors_cleanup(void) +{ + return 0; +} + +#ifdef MODULE + +MODULE_AUTHOR("Frodo Looijaard "); +MODULE_DESCRIPTION("LM78 driver"); + +int init_module(void) +{ + return sensors_init(); +} + +int cleanup_module(void) +{ + return sensors_cleanup(); +} + +#endif /* MODULE */ + diff --git a/src/Module.mk b/src/Module.mk index 454d3bac..39ce664c 100644 --- a/src/Module.mk +++ b/src/Module.mk @@ -23,16 +23,23 @@ MODULE_DIR := src # Regrettably, even 'simply expanded variables' will not put their currently # defined value verbatim into the command-list of rules... SRCTARGETS := $(MODULE_DIR)/smbus.o $(MODULE_DIR)/piix4.o $(MODULE_DIR)/isa.o \ - $(MODULE_DIR)/lm78.o $(MODULE_DIR)/i2c-proc.o + $(MODULE_DIR)/lm78.o $(MODULE_DIR)/sensors.o \ + $(MODULE_DIR)/i2c-proc.o + +HEADERFILES := $(MODULE_DIR)/sensors.h # Include all dependency files INCLUDEFILES += $(SRCTARGETS:.o=.d) -all :: $(SRCTARGETS) +all-src: $(SRCTARGETS) +all :: all-src -install :: +install-src: $(MKDIR) $(MODDIR) install -o root -g root -m 644 $(SRCTARGETS) $(MODDIR) + install -o root -g root -m 644 $(HEADERFILES) $(INCLUDEDIR) +install :: install-src -clean :: +clean-src: $(RM) $(SRCTARGETS) $(SRCTARGETS:.o=.d) +clean :: clean-src diff --git a/src/compat.h b/src/compat.h index c6b5049e..e3adedba 100644 --- a/src/compat.h +++ b/src/compat.h @@ -37,4 +37,20 @@ #endif #endif /* def MODULE */ +/* copy_from/to_usr is called memcpy_from/to_fs in 2.0 kernels; perhaps in + some early 2.1 kernels too? */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,1,4)) +#define copy_from_user memcpy_fromfs +#define copy_to_user memcpy_tofs +#endif + +/* get_user was redefined in 2.1 kernels to use two arguments, and returns + an error code */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,1,4)) +#define get_user_data(to,from) ((to) = get_user(from),0) +#else +#define get_user_data(to,from) get_user(to,from) +#endif + + #endif /* SENSORS_COMPAT_H */ diff --git a/src/lm78.c b/src/lm78.c index 8a469713..6a1b7a98 100644 --- a/src/lm78.c +++ b/src/lm78.c @@ -1,3 +1,4 @@ + /* lm78.c - A Linux module for reading sensor data. Copyright (c) 1998 Frodo Looijaard @@ -21,15 +22,112 @@ #include #include #include +#include #include #include #include -#include "lm78.h" #include "smbus.h" #include "version.h" #include "isa.h" #include "sensors.h" +#include "i2c.h" +#include "compat.h" +/* Many LM78 constants needed 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) (0x3a + (nr)) +#define LM78_REG_FAN(nr) (0x27 + (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 + + +/* Conversions */ +static int lm78_in_conv[7] = {10000, 10000, 10000, 16892, 38000, + -34768, -15050 }; +#define IN_TO_REG(val,nr) ((((val) * 100000 / lm78_in_conv[nr]) + 8) / 16) +#define IN_FROM_REG(val,nr) (((val) * 16 * lm78_in_conv[nr]) / 100000) + +#define FAN_TO_REG(val) (((val)==0)?255:((1350000+(val))/((val)*2))) +#define FAN_FROM_REG(val) (((val)==0)?-1:\ + ((val)==255)?0:(1350000 + (val))/((val)*2)) + +#define TEMP_TO_REG(val) ((val)<0?(val)&0xff:(val)) +#define TEMP_FROM_REG(val) ((val)>0x80?(val)-0x100:(val)); + +#define VID_FROM_REG(val) ((val) == 0x0f?0:350-(val)*10) + +#define ALARMS_FROM_REG(val) (val) + +#define DIV_FROM_REG(val) (1 >> (val)) +#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?1:2) + +/* Initial limits */ +#define LM78_INIT_IN_0 280 +#define LM78_INIT_IN_1 280 +#define LM78_INIT_IN_2 330 +#define LM78_INIT_IN_3 500 +#define LM78_INIT_IN_4 1200 +#define LM78_INIT_IN_5 -1200 +#define LM78_INIT_IN_6 -500 + +#define LM78_INIT_IN_PERCENTAGE 100 + +#define LM78_INIT_IN_MIN_0 \ + (LM78_INIT_IN_0 - LM78_INIT_IN_0 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_0 \ + (LM78_INIT_IN_0 + LM78_INIT_IN_0 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_1 \ + (LM78_INIT_IN_1 - LM78_INIT_IN_1 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_1 \ + (LM78_INIT_IN_1 + LM78_INIT_IN_1 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_2 \ + (LM78_INIT_IN_2 - LM78_INIT_IN_2 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_2 \ + (LM78_INIT_IN_2 + LM78_INIT_IN_2 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_3 \ + (LM78_INIT_IN_3 - LM78_INIT_IN_3 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_3 \ + (LM78_INIT_IN_3 + LM78_INIT_IN_3 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_4 \ + (LM78_INIT_IN_4 - LM78_INIT_IN_4 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_4 \ + (LM78_INIT_IN_4 + LM78_INIT_IN_4 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_5 \ + (LM78_INIT_IN_5 - LM78_INIT_IN_5 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_5 \ + (LM78_INIT_IN_5 + LM78_INIT_IN_5 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MIN_6 \ + (LM78_INIT_IN_6 - LM78_INIT_IN_6 * LM78_INIT_IN_PERCENTAGE / 100) +#define LM78_INIT_IN_MAX_6 \ + (LM78_INIT_IN_6 + LM78_INIT_IN_6 * LM78_INIT_IN_PERCENTAGE / 100) + +#define LM78_INIT_FAN_MIN_1 3000 +#define LM78_INIT_FAN_MIN_2 3000 +#define LM78_INIT_FAN_MIN_3 3000 + +#define LM78_INIT_TEMP_OVER 60 +#define LM78_INIT_TEMP_HYST 50 #ifdef MODULE extern int init_module(void); @@ -51,6 +149,32 @@ extern int cleanup_module(void); 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 semaphore lock; + int sysctl_id; + + 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 */ + u8 temp; /* Register value */ + u8 temp_over; /* Register value */ + u8 temp_hyst; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u8 vid; /* Register encoding, combined */ + u16 alarms; /* Register encoding, combined */ +}; + + static int lm78_init(void); static int lm78_cleanup(void); @@ -62,21 +186,44 @@ static int lm78_detach_isa(struct isa_client *client); static int lm78_detach_smbus(struct i2c_client *client); static int lm78_new_client(struct i2c_adapter *adapter, struct i2c_client *new_client); -static void lm78_init_client(struct i2c_client *client); static void lm78_remove_client(struct i2c_client *client); static int lm78_command(struct i2c_client *client, unsigned int cmd, void *arg); static void lm78_inc_use (struct i2c_client *client); static void lm78_dec_use (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 void lm78_update_client(struct i2c_client *client); +static void lm78_init_client(struct i2c_client *client); + +static int lm78_sysctl (ctl_table *table, int *name, int nlen, void *oldval, + size_t *oldlenp, void *newval, size_t newlen, + void **context); +static int lm78_proc (ctl_table *ctl, int write, struct file * filp, + void *buffer, size_t *lenp); + + +static void write_in(struct i2c_client *client, int nr, int nrels, + long *results); +static void read_in(struct i2c_client *client, int nr, long *results); +static void write_fan(struct i2c_client *client, int nr, int nrels, + long *results); +static void read_fan(struct i2c_client *client, int nr, long *results); +static void write_temp(struct i2c_client *client, int nrels, long *results); +static void read_temp(struct i2c_client *client, long *results); +static void read_vid(struct i2c_client *client, long *results); +static void read_alarms(struct i2c_client *client, long *results); +static void write_fan_div(struct i2c_client *client, int nrels, long *results); +static void read_fan_div(struct i2c_client *client, long *results); + + /* I choose here for semi-static LM78 allocation. Complete dynamic allocation could also be used; the code needed for this would probably - take more memory than the datastructures take now */ + take more memory than the datastructure takes now. */ #define MAX_LM78_NR 4 static struct i2c_client *lm78_list[MAX_LM78_NR]; -static struct semaphore lm78_semaphores[MAX_LM78_NR]; /* The driver. I choose to use type i2c_driver, as at is identical to both smbus_driver and isa_driver, and clients could be of either kind */ @@ -94,8 +241,34 @@ static struct i2c_driver lm78_driver = { /* Used by lm78_init/cleanup */ static int lm78_initialized = 0; +/* The /proc/sys entries */ +/* These files are created for each detected LM78. 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 lm78_dir_table_template[] = { + { LM78_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_FAN2, "fan1", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_FAN3, "fan1", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_VID, "vid", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + { LM78_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl }, + + { LM78_SYSCTL_ALARMS, "alarms", NULL, 0, 0644, NULL, &lm78_proc, &lm78_sysctl } +}; + + /* This function is called when: - * lm78_driver is inserted, for each available adapter + * 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) */ int lm78_attach_adapter(struct i2c_adapter *adapter) { @@ -105,7 +278,10 @@ int lm78_attach_adapter(struct i2c_adapter *adapter) return lm78_detect_smbus(adapter); } -/* This function is called whenever a client should be removed */ +/* This function is called whenever a client should be removed: + * lm78_driver is removed (when this module is unloaded) + * when an adapter is removed which has a lm78 client (and lm78_driver + is still present). */ int lm78_detach_client(struct i2c_client *client) { if (i2c_is_isa_client(client)) @@ -138,42 +314,74 @@ int lm78_detect_isa(struct isa_adapter *adapter) request_region(address, LM78_EXTENT, "lm78"); - if (! (new_client = kmalloc(sizeof(struct isa_client), GFP_KERNEL))) + /* Allocate space for a new client structure */ + if (! (new_client = kmalloc(sizeof(struct isa_client) + + sizeof(struct lm78_data), + GFP_KERNEL))) { - release_region(address,LM78_EXTENT); err=-ENOMEM; - continue; - } + goto ERROR1; + } + + /* Fill the new client structure with data */ + new_client->data = (struct lm78_data *) (new_client + 1); new_client->addr = 0; new_client->isa_addr = address; if ((err = lm78_new_client((struct i2c_adapter *) adapter, - (struct i2c_client *) new_client))) { - release_region(address, LM78_EXTENT); - kfree(new_client); - continue; - } - if ((err = isa_attach_client(new_client))) { - release_region(address, LM78_EXTENT); - lm78_remove_client((struct i2c_client *) new_client); - kfree(new_client); - continue; - } + (struct i2c_client *) new_client))) + goto ERROR2; + + /* Tell i2c-core a new client has arrived */ + if ((err = isa_attach_client(new_client))) + goto ERROR3; + + /* Register a new directory entry with module sensors */ + if ((err = sensors_register_entry((struct i2c_client *) new_client,"lm78", + lm78_dir_table_template) < 0)) + goto ERROR4; + ((struct lm78_data *) (new_client->data)) -> sysctl_id = err; + + /* Initialize the LM78 chip */ lm78_init_client((struct i2c_client *) new_client); + continue; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + +ERROR4: + isa_detach_client(new_client); +ERROR3: + lm78_remove_client((struct i2c_client *) new_client); +ERROR2: + kfree(new_client); +ERROR1: + release_region(address, LM78_EXTENT); } return err; + } /* Deregister and remove a LM78 client */ int lm78_detach_isa(struct isa_client *client) { - int err; + int err,i; + for (i = 0; i < MAX_LM78_NR; i++) + if ((client == (struct isa_client *) (lm78_list[i]))) + break; + if (i == MAX_LM78_NR) { + printk("lm78.o: Client to detach not found.\n"); + return -ENOENT; + } + + sensors_deregister_entry(((struct lm78_data *)(client->data))->sysctl_id); + if ((err = isa_detach_client(client))) { printk("lm78.o: Client deregistration failed, client not detached.\n"); return err; } - release_region(client->isa_addr,LM78_EXTENT); lm78_remove_client((struct i2c_client *) client); kfree(client); + release_region(client->isa_addr,LM78_EXTENT); return 0; } @@ -194,25 +402,59 @@ int lm78_detect_smbus(struct i2c_adapter *adapter) /* Real detection code goes here */ - new_client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + /* Allocate space for a new client structure */ + if (! (new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct lm78_data), + GFP_KERNEL))) { + err = -ENOMEM; + continue; + } + + /* Fill the new client structure with data */ + new_client->data = (struct lm78_data *) (new_client + 1); new_client->addr = address; - if ((err = lm78_new_client(adapter,new_client))) { - kfree(new_client); - continue; - } - if ((err = i2c_attach_client(new_client))) { - lm78_remove_client(new_client); - kfree(new_client); - continue; - } + if ((err = lm78_new_client(adapter,new_client))) + goto ERROR2; + + /* Tell i2c-core a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto ERROR3; + + /* Register a new directory entry with module sensors */ + if ((err = sensors_register_entry(new_client,"lm78", + lm78_dir_table_template) < 0)) + goto ERROR4; + ((struct lm78_data *) (new_client->data))->sysctl_id = err; + + /* Initialize the LM78 chip */ lm78_init_client(new_client); + continue; + +/* 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: + lm78_remove_client((struct i2c_client *) new_client); +ERROR2: + kfree(new_client); } return err; } int lm78_detach_smbus(struct i2c_client *client) { - int err; + int err,i; + for (i = 0; i < MAX_LM78_NR; i++) + if (client == lm78_list[i]) + break; + if ((i == MAX_LM78_NR)) { + printk("lm78.o: Client to detach not found.\n"); + return -ENOENT; + } + + sensors_deregister_entry(((struct lm78_data *)(client->data))->sysctl_id); + if ((err = i2c_detach_client(client))) { printk("lm78.o: Client deregistration failed, client not detached.\n"); return err; @@ -228,6 +470,7 @@ int lm78_new_client(struct i2c_adapter *adapter, struct i2c_client *new_client) { int i; + struct lm78_data *data; /* First, seek out an empty slot */ for(i = 0; i < MAX_LM78_NR; i++) @@ -240,28 +483,26 @@ int lm78_new_client(struct i2c_adapter *adapter, } lm78_list[i] = new_client; - lm78_semaphores[i] = MUTEX; - new_client->data = &lm78_semaphores[i]; strcpy(new_client->name,"LM78 chip"); new_client->id = i; new_client->adapter = adapter; new_client->driver = &lm78_driver; + data = new_client->data; + data->valid = 0; + data->lock = MUTEX; + data->update_lock = MUTEX; return 0; } +/* Inverse of lm78_new_client */ void lm78_remove_client(struct i2c_client *client) { int i; for (i = 0; i < MAX_LM78_NR; i++) - if (client == lm78_list[i]) + if (client == lm78_list[i]) lm78_list[i] = NULL; } -/* Called when we have found a new LM78. It should set limits, etc. */ -void lm78_init_client(struct i2c_client *client) -{ -} - /* No commands defined yet */ int lm78_command(struct i2c_client *client, unsigned int cmd, void *arg) { @@ -316,37 +557,346 @@ int lm78_write_value(struct i2c_client *client, u8 reg, u8 value) return smbus_write_byte_data(client->adapter, client->addr, reg,value); } -/* ANYTHING BELOW IS JUST AN EXAMPLE. IGNORE IF YOU WANT TO BASE A DRIVER - ON THE CODE IN THIS FILE */ - -/* Stupid entry in /proc */ -static int proc_function(char *buf, char **start, off_t offset, int len, - int unused) +/* Called when we have found a new LM78. It should set limits, etc. */ +void lm78_init_client(struct i2c_client *client) { - int i; - len = 0; - for (i = 0; i < MAX_LM78_NR; i++) - if(lm78_list[i]) { - if(i2c_is_isa_client(lm78_list[i])) - len += sprintf(buf+len,"(isa) %d: address=%x\n",i, - ((struct isa_client *) (lm78_list[i]))->isa_addr); - else - len += sprintf(buf+len,"(i2c) %d: address=%x\n",i, - lm78_list[i]->addr); - } - return len; + /* Reset all except Watchdog values and last conversion values + This sets fan-divs to 2, among others */ + lm78_write_value(client,LM78_REG_CONFIG,0x80); + + lm78_write_value(client,LM78_REG_IN_MIN(0),IN_TO_REG(LM78_INIT_IN_MIN_0,0)); + lm78_write_value(client,LM78_REG_IN_MAX(0),IN_TO_REG(LM78_INIT_IN_MAX_0,0)); + lm78_write_value(client,LM78_REG_IN_MIN(1),IN_TO_REG(LM78_INIT_IN_MIN_1,1)); + lm78_write_value(client,LM78_REG_IN_MAX(1),IN_TO_REG(LM78_INIT_IN_MAX_1,1)); + lm78_write_value(client,LM78_REG_IN_MIN(2),IN_TO_REG(LM78_INIT_IN_MIN_2,2)); + lm78_write_value(client,LM78_REG_IN_MAX(2),IN_TO_REG(LM78_INIT_IN_MAX_2,2)); + lm78_write_value(client,LM78_REG_IN_MIN(3),IN_TO_REG(LM78_INIT_IN_MIN_3,3)); + lm78_write_value(client,LM78_REG_IN_MAX(3),IN_TO_REG(LM78_INIT_IN_MAX_3,3)); + lm78_write_value(client,LM78_REG_IN_MIN(4),IN_TO_REG(LM78_INIT_IN_MIN_4,4)); + lm78_write_value(client,LM78_REG_IN_MAX(4),IN_TO_REG(LM78_INIT_IN_MAX_4,4)); + lm78_write_value(client,LM78_REG_IN_MIN(5),IN_TO_REG(LM78_INIT_IN_MIN_5,5)); + lm78_write_value(client,LM78_REG_IN_MAX(5),IN_TO_REG(LM78_INIT_IN_MAX_5,5)); + lm78_write_value(client,LM78_REG_IN_MIN(6),IN_TO_REG(LM78_INIT_IN_MIN_6,6)); + lm78_write_value(client,LM78_REG_IN_MAX(6),IN_TO_REG(LM78_INIT_IN_MAX_6,6)); + lm78_write_value(client,LM78_REG_FAN_MIN(1),FAN_TO_REG(LM78_INIT_FAN_MIN_1)); + lm78_write_value(client,LM78_REG_FAN_MIN(2),FAN_TO_REG(LM78_INIT_FAN_MIN_2)); + lm78_write_value(client,LM78_REG_FAN_MIN(3),FAN_TO_REG(LM78_INIT_FAN_MIN_3)); + lm78_write_value(client,LM78_REG_TEMP_OVER,TEMP_TO_REG(LM78_INIT_TEMP_OVER)); + lm78_write_value(client,LM78_REG_TEMP_HYST,TEMP_TO_REG(LM78_INIT_TEMP_HYST)); + + /* Start monitoring */ + lm78_write_value(client,LM78_REG_CONFIG, + (lm78_read_value(client,LM78_REG_CONFIG) & 0xf7) | 0x01); + } -/* OK, this is a test entry. Just ignore, it is not important. */ -static struct proc_dir_entry proc_entry = - { - 0,12,"sensors-test", - S_IFREG | S_IRUGO, 1, 0, 0, - 0, NULL, - &proc_function - }; +void lm78_update_client(struct i2c_client *client) +{ + struct lm78_data *data = client->data; + int i; + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ+HZ/2 ) || + (jiffies < data->last_updated) || ! data->valid) { + + 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 = 1; i <= 3; i++) { + data->fan[i-1] = lm78_read_value(client,LM78_REG_FAN(i)); + data->fan_min[i-1] = 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; + 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; + } + + up(&data->update_lock); +} + +/* This function is called when /proc/sys/dev/lm78-???/... is accessed */ +int lm78_proc (ctl_table *ctl, int write, struct file * filp, + void *buffer, size_t *lenp) +{ + int nrels,mag; + long results[7]; + struct i2c_client *client = ctl -> extra1; + + /* If buffer is size 0, or we try to read when not at the start, we + return nothing. Note that I think writing when not at the start + does not work either, but anyway, this is straight from the kernel + sources. */ + if (!*lenp || (filp->f_pos && !write)) { + *lenp = 0; + return 0; + } + + /* How many numbers are found within these files, and how to scale them? */ + switch (ctl->ctl_name) { + case LM78_SYSCTL_IN0: case LM78_SYSCTL_IN1: case LM78_SYSCTL_IN2: + case LM78_SYSCTL_IN3: case LM78_SYSCTL_IN4: case LM78_SYSCTL_IN5: + case LM78_SYSCTL_IN6: + nrels=3; + mag=2; + break; + case LM78_SYSCTL_FAN_DIV: case LM78_SYSCTL_TEMP: + nrels=3; + mag=0; + break; + case LM78_SYSCTL_FAN1: case LM78_SYSCTL_FAN2: case LM78_SYSCTL_FAN3: + nrels=2; + mag=0; + break; + case LM78_SYSCTL_VID: + nrels=1; + mag=2; + break; + case LM78_SYSCTL_ALARMS: + nrels=1; + mag=0; + break; + default: /* Should never be called */ + return -EINVAL; + } + + /* OK, try writing stuff. */ + if (write) { + sensors_parse_reals(&nrels,buffer,*lenp,results,mag); + if (nrels == 0) + return 0; + switch (ctl->ctl_name) { + case LM78_SYSCTL_IN0: write_in(client,0,nrels,results); break; + case LM78_SYSCTL_IN1: write_in(client,1,nrels,results); break; + case LM78_SYSCTL_IN2: write_in(client,2,nrels,results); break; + case LM78_SYSCTL_IN3: write_in(client,3,nrels,results); break; + case LM78_SYSCTL_IN4: write_in(client,4,nrels,results); break; + case LM78_SYSCTL_IN5: write_in(client,5,nrels,results); break; + case LM78_SYSCTL_IN6: write_in(client,6,nrels,results); break; + case LM78_SYSCTL_FAN1: write_fan(client,1,nrels,results); break; + case LM78_SYSCTL_FAN2: write_fan(client,2,nrels,results); break; + case LM78_SYSCTL_FAN3: write_fan(client,3,nrels,results); break; + case LM78_SYSCTL_FAN_DIV: write_fan_div(client,nrels,results);break; + case LM78_SYSCTL_TEMP: write_temp(client,nrels,results);break; + case LM78_SYSCTL_VID: case LM78_SYSCTL_ALARMS: break; + default: /* Should never be called */ *lenp=0; return -EINVAL; break; + } + filp->f_pos += *lenp; + return 0; + } else { /* read */ + /* Update all values in LM_Sensor_Data */ + + lm78_update_client((struct i2c_client *) (ctl->extra1)); + + /* Read the values to print into results */ + switch (ctl->ctl_name) { + case LM78_SYSCTL_IN0: read_in(client,0,results);break; + case LM78_SYSCTL_IN1: read_in(client,1,results);break; + case LM78_SYSCTL_IN2: read_in(client,2,results);break; + case LM78_SYSCTL_IN3: read_in(client,3,results);break; + case LM78_SYSCTL_IN4: read_in(client,4,results);break; + case LM78_SYSCTL_IN5: read_in(client,5,results);break; + case LM78_SYSCTL_IN6: read_in(client,6,results);break; + case LM78_SYSCTL_FAN1: read_fan(client,1,results);break; + case LM78_SYSCTL_FAN2: read_fan(client,2,results);break; + case LM78_SYSCTL_FAN3: read_fan(client,3,results);break; + case LM78_SYSCTL_TEMP: read_temp(client,results);break; + case LM78_SYSCTL_FAN_DIV: read_fan_div(client,results);break; + case LM78_SYSCTL_VID: read_vid(client,results);break; + case LM78_SYSCTL_ALARMS: read_alarms(client,results);break; + default: /* Should never be called */ return -EINVAL; + } + /* OK, print it now */ + sensors_write_reals(nrels,buffer,lenp,results,mag); + filp->f_pos += *lenp; + return 0; + } +} + +/* This function is called when a sysctl on a lm78 file is done */ +int lm78_sysctl (ctl_table *table, int *name, int nlen, void *oldval, + size_t *oldlenp, void *newval, size_t newlen, + void **context) +{ + long results[7]; + int nrels,oldlen; + struct i2c_client *client = table -> extra1; + + /* How many numbers are found within these files, and how to scale them? */ + switch (table->ctl_name) { + case LM78_SYSCTL_IN0: case LM78_SYSCTL_IN1: case LM78_SYSCTL_IN2: + case LM78_SYSCTL_IN3: case LM78_SYSCTL_IN4: case LM78_SYSCTL_IN5: + case LM78_SYSCTL_IN6: case LM78_SYSCTL_TEMP: case LM78_SYSCTL_FAN_DIV: + nrels=3; + break; + case LM78_SYSCTL_FAN1: case LM78_SYSCTL_FAN2: case LM78_SYSCTL_FAN3: + nrels=2; + break; + case LM78_SYSCTL_VID: case LM78_SYSCTL_ALARMS: + nrels=1; + break; + default: /* Should never be called */ + return -EINVAL; + } + + /* Check if we need to output the old values */ + if (oldval && oldlenp && ! get_user_data(oldlen,oldlenp) && oldlen) { + + /* Update all values in LM_Sensor_Data */ + lm78_update_client((struct i2c_client *) (table->extra1)); + switch (table->ctl_name) { + case LM78_SYSCTL_IN0: read_in(client,0,results);break; + case LM78_SYSCTL_IN1: read_in(client,1,results);break; + case LM78_SYSCTL_IN2: read_in(client,2,results);break; + case LM78_SYSCTL_IN3: read_in(client,3,results);break; + case LM78_SYSCTL_IN4: read_in(client,4,results);break; + case LM78_SYSCTL_IN5: read_in(client,5,results);break; + case LM78_SYSCTL_IN6: read_in(client,6,results);break; + case LM78_SYSCTL_FAN1: read_fan(client,1,results);break; + case LM78_SYSCTL_FAN2: read_fan(client,2,results);break; + case LM78_SYSCTL_FAN3: read_fan(client,3,results);break; + case LM78_SYSCTL_TEMP: read_temp(client,results);break; + case LM78_SYSCTL_FAN_DIV: read_fan_div(client,results);break; + case LM78_SYSCTL_VID: read_vid(client,results);break; + case LM78_SYSCTL_ALARMS: read_alarms(client,results);break; + default: /* Should never be called */ return -EINVAL; + } + /* Note the rounding factor! */ + if (nrels * sizeof(long) < oldlen) + oldlen = nrels * sizeof(long); + oldlen = (oldlen / sizeof(long)) * sizeof(long); + copy_to_user(oldval,results,oldlen); + put_user(oldlen,oldlenp); + } + + /* Check to see whether we need to read the new values */ + if (newval && newlen) { + if (nrels * sizeof(long) < newlen) + newlen = nrels * sizeof(long); + nrels = newlen / sizeof(long); + newlen = (newlen / sizeof(long)) * sizeof(long); + copy_from_user(results,newval,newlen); + + switch (table->ctl_name) { + case LM78_SYSCTL_IN0: write_in(client,0,nrels,results); break; + case LM78_SYSCTL_IN1: write_in(client,1,nrels,results); break; + case LM78_SYSCTL_IN2: write_in(client,2,nrels,results); break; + case LM78_SYSCTL_IN3: write_in(client,3,nrels,results); break; + case LM78_SYSCTL_IN4: write_in(client,4,nrels,results); break; + case LM78_SYSCTL_IN5: write_in(client,5,nrels,results); break; + case LM78_SYSCTL_IN6: write_in(client,6,nrels,results); break; + case LM78_SYSCTL_FAN1: write_fan(client,1,nrels,results); break; + case LM78_SYSCTL_FAN2: write_fan(client,2,nrels,results); break; + case LM78_SYSCTL_FAN3: write_fan(client,3,nrels,results); break; + case LM78_SYSCTL_TEMP: write_temp(client,nrels,results); break; + case LM78_SYSCTL_FAN_DIV: write_fan_div(client,nrels,results);break; + case LM78_SYSCTL_VID: case LM78_SYSCTL_ALARMS: break; + default: /* Should never be called */ return -EINVAL; break; + } + } + return 1; /* We have done all the work */ +} + +void write_in(struct i2c_client *client, int nr, int nrels, long *results) +{ + struct lm78_data *data = client->data; + if (nrels >= 1) { + data->in_min[nr] = IN_TO_REG(results[0],nr); + lm78_write_value(client,LM78_REG_IN_MIN(nr),data->in_min[nr]); + } + if (nrels >= 2) { + data->in_max[nr] = IN_TO_REG(results[1],nr); + lm78_write_value(client,LM78_REG_IN_MAX(nr),data->in_max[nr]); + } +} + +void read_in(struct i2c_client *client, int nr, long *results) +{ + struct lm78_data *data = client->data; + results[0] = IN_FROM_REG(data->in_min[nr],nr); + results[1] = IN_FROM_REG(data->in_min[nr],nr); + results[2] = IN_FROM_REG(data->in_min[nr],nr); +} + +void write_fan(struct i2c_client *client, int nr, int nrels, long *results) +{ + struct lm78_data *data = client->data; + if (nrels >= 1) { + data->fan_min[nr-1] = FAN_TO_REG(results[0]); + lm78_write_value(client,LM78_REG_FAN_MIN(nr),data->fan_min[nr-1]); + } +} + +void read_fan(struct i2c_client *client, int nr, long *results) +{ + struct lm78_data *data = client->data; + results[0] = FAN_FROM_REG(data->fan_min[nr-1]); + results[1] = FAN_FROM_REG(data->fan[nr-1]); +} + +void write_temp(struct i2c_client *client, int nrels, long *results) +{ + struct lm78_data *data = client->data; + if (nrels >= 1) { + data->temp_over = TEMP_TO_REG(results[0]); + lm78_write_value(client,LM78_REG_TEMP_OVER,data->temp_over); + } + if (nrels >= 2) { + data->temp_hyst = TEMP_TO_REG(results[0]); + lm78_write_value(client,LM78_REG_TEMP_HYST,data->temp_hyst); + } +} + +void read_temp(struct i2c_client *client, long *results) +{ + struct lm78_data *data = client->data; + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); +} + +void read_vid(struct i2c_client *client, long *results) +{ + struct lm78_data *data = client->data; + results[0] = VID_FROM_REG(data->vid); +} + +void read_alarms(struct i2c_client *client, long *results) +{ + struct lm78_data *data = client->data; + results[0] = ALARMS_FROM_REG(data->alarms); +} + +void read_fan_div(struct i2c_client *client, long *results) +{ + struct lm78_data *data = client->data; + results[0] = DIV_FROM_REG(data->fan_div[0]); + results[1] = DIV_FROM_REG(data->fan_div[1]); +} + +void write_fan_div(struct i2c_client *client, int nrels, long *results) +{ + struct lm78_data *data = client->data; + if (nrels >= 2) + data->fan_div[1] = DIV_TO_REG(results[1]); + if (nrels >= 1) { + data->fan_div[0] = DIV_TO_REG(results[0]); + lm78_write_value(client,LM78_REG_VID_FANDIV, + (data->fan_div[0] >> 4) | (data->fan_div[1] >> 6)); + } +} + int lm78_init(void) { int res; @@ -354,16 +904,6 @@ int lm78_init(void) printk("lm78.o version %s (%s)\n",LM_VERSION,LM_DATE); lm78_initialized = 0; - /* OK, we register some stupid proc file here. This is *just* *temporary*, - for test purposes. Ignore if you want. Only works for kernels 2.0.x. */ - if ((res = proc_register_dynamic(&proc_root,&proc_entry))) { - printk("lm78.o: Couldn't create /proc/sensors-test, " - "module not inserted.\n"); - lm78_cleanup(); - return res; - } - lm78_initialized ++; - if ((res =i2c_add_driver(&lm78_driver))) { printk("lm78.o: Driver registration failed, module not inserted.\n"); lm78_cleanup(); @@ -377,7 +917,7 @@ int lm78_cleanup(void) { int res; - if (lm78_initialized >= 2) { + if (lm78_initialized >= 1) { if ((res = i2c_del_driver(&lm78_driver))) { printk("lm78.o: Driver deregistration failed, module not removed.\n"); return res; @@ -385,14 +925,6 @@ int lm78_cleanup(void) } else lm78_initialized --; - if (lm78_initialized >= 1) { - if((res = proc_unregister(&proc_root,proc_entry.low_ino))) { - printk("lm78.o: Deregistration of /proc/sensors_test failed, " - "module not removed.\n"); - return res; - } - } else - lm78_initialized --; return 0; } diff --git a/src/lm78.h b/src/lm78.h deleted file mode 100644 index 47a3ee39..00000000 --- a/src/lm78.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - lm78.h - A Linux module for reading sensor data. - Copyright (c) 1998 Frodo Looijaard - - 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. -*/ - -#ifndef SENSORS_SRC_LM78 -#define SENSORS_SRC_LM78 - -/* 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 - -#endif /* ndef SENSORS_SRC_LM78 */ diff --git a/src/sensors.c b/src/sensors.c new file mode 100644 index 00000000..322da300 --- /dev/null +++ b/src/sensors.c @@ -0,0 +1,346 @@ +/* + sensors.c - A Linux module for reading sensor data. + Copyright (c) 1998 Frodo Looijaard + + 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 + +#include "version.h" +#include "compat.h" +#include "i2c.h" +#include "isa.h" +#include "sensors.h" + +#ifdef MODULE +extern int init_module(void); +extern int cleanup_module(void); +#endif /* MODULE */ + +int sensors_register_entry(struct i2c_client *client ,const char *prefix, + ctl_table *ctl_template); +static int sensors_create_name(char **name, const char *prefix, + struct i2c_adapter * adapter, int addr); +static int sensors_init(void); +static int sensors_cleanup(void); + +#define SENSORS_ENTRY_MAX 20 +static struct ctl_table_header *sensors_entries[SENSORS_ENTRY_MAX]; + +static ctl_table sysctl_table[] = { + { CTL_DEV, "dev", NULL, 0, 0555 }, + { 0 }, + { DEV_SENSORS, "sensors", NULL, 0, 0555 }, + { 0 }, + { 0, NULL, NULL, 0, 0555 }, + { 0 } +}; + +/* This returns a nice name for a new directory; for example lm78-isa-0310 + (for a LM78 chip on the ISA bus at port 0x310), or lm75-i2c-3-4e (for + a LM75 chip on the third i2c bus at address 0x4e). + name is allocated first. */ +int sensors_create_name(char **name, const char *prefix, + struct i2c_adapter * adapter, int addr) +{ + char name_buffer[50]; + int id; + if (i2c_is_isa_adapter(adapter)) + sprintf(name_buffer,"%s-isa-%d",prefix,addr); + else { + if ((id = i2c_adapter_id(adapter)) < 0); + return -ENOENT; + sprintf(name_buffer,"%s-i2c-%d-%d",prefix,id,addr); + } + *name = kmalloc(strlen(name_buffer)+1,GFP_KERNEL); + strcpy(*name,name_buffer); + return 0; +} + +/* This rather complex function must be called when you want to add an entry + to /proc/sys/dev/sensors/chips (not yet implemented). It also creates + a new directory within /proc/sys/dev/sensors/. + ctl_template should be a template of the newly created directory. It is + copied in memory. The extra1 field of each file is set to point to client. + If any driver wants subdirectories within the newly created directory, + this function must be updated! */ +int sensors_register_entry(struct i2c_client *client ,const char *prefix, + ctl_table *ctl_template) +{ + int i,res,len,id; + ctl_table *new_table; + char *name; + struct ctl_table_header *new_header; + + if ((res = sensors_create_name(&name,prefix,client->adapter, + i2c_is_isa_client(client)? + ((struct isa_client *) client)->isa_addr: + client->addr))) + return res; + + for (id = 0; id < SENSORS_ENTRY_MAX; i++) + if (! sensors_entries[id]) { + break; + } + if (id == SENSORS_ENTRY_MAX) { + kfree(name); + return -ENOMEM; + } + id += 256; + + len = 0; + while (ctl_template[len].procname) + len++; + len += 7; + if (! (new_table = kmalloc(sizeof(ctl_table) * len,GFP_KERNEL))) { + kfree(name); + return -ENOMEM; + } + + memcpy(new_table,sysctl_table,6 * sizeof(ctl_table)); + new_table[0].child = &new_table[2]; + new_table[2].child = &new_table[4]; + new_table[4].child = &new_table[6]; + new_table[4].procname = name; + new_table[4].ctl_name = id; + memcpy(new_table+6,ctl_template,(len-6) * sizeof(ctl_table)); + for (i = 6; i < len; i++) + new_table[i].extra1 = client; + + if (! (new_header = register_sysctl_table(new_table,0))) { + kfree(new_table); + kfree(name); + return -ENOMEM; + } + + sensors_entries[i] = new_header; + + return id; +} + +void sensors_deregister_entry(int id) +{ + ctl_table *table; + id -= 256; + if (sensors_entries[id]) { + table = sensors_entries[id]->ctl_table; + unregister_sysctl_table(sensors_entries[id]); + kfree((void *) (table->procname)); + kfree(table); + sensors_entries[id] = 0; + } +} + +/* nrels contains initially the maximum number of elements which can be + put in results, and finally the number of elements actually put there. + A magnitude of 1 will multiply everything with 10; etc. + buffer, bufsize is the character buffer we read from and its length. + results will finally contain the parsed integers. + + Buffer should contain several reals, separated by whitespace. A real + has the following syntax: + [ Minus ] Digit* [ Dot Digit* ] + (everything between [] is optional; * means zero or more). + When the next character is unparsable, everything is skipped until the + next whitespace. + + WARNING! This is tricky code. I have tested it, but there may still be + hidden bugs in it, even leading to crashes and things! +*/ +void sensors_parse_reals(int *nrels, void *buffer, int bufsize, + long *results, int magnitude) +{ + int maxels,min,mag; + long res; + char nextchar=0; + + maxels = *nrels; + *nrels = 0; + + while (bufsize && (*nrels < maxels)) { + + /* Skip spaces at the start */ + while (bufsize && ! get_user_data(nextchar,(char *) buffer) && + isspace((int) nextchar)) { + bufsize --; + ((char *) buffer)++; + } + + /* Well, we may be done now */ + if (! bufsize) + return; + + /* New defaults for our result */ + min = 0; + res = 0; + mag = magnitude; + + /* Check for a minus */ + if (! get_user_data(nextchar,(char *) buffer) && (nextchar == '-')) { + min=1; + bufsize--; + ((char *) buffer)++; + } + + /* Digits before a decimal dot */ + while (bufsize && !get_user_data(nextchar,(char *) buffer) && + isdigit((int) nextchar)) { + res = res * 10 + nextchar - '0'; + bufsize--; + ((char *) buffer)++; + } + + /* If mag < 0, we must actually divide here! */ + while (mag < 0) { + res = res / 10; + mag++; + } + + if (bufsize && (nextchar == '.')) { + /* Skip the dot */ + bufsize--; + ((char *) buffer)++; + + /* Read digits while they are significant */ + while(bufsize && (mag > 0) && + !get_user_data(nextchar,(char *) buffer) && + isdigit((int) nextchar)) { + res = res * 10 + nextchar - '0'; + mag--; + bufsize--; + ((char *) buffer)++; + } + } + /* If we are out of data, but mag > 0, we need to scale here */ + while (mag > 0) { + res = res * 10; + mag --; + } + + /* Skip everything until we hit whitespace */ + while(bufsize && !get_user_data(nextchar,(char *) buffer) && + isspace ((int) nextchar)) { + bufsize --; + ((char *) buffer) ++; + } + + /* Put res in results */ + results[*nrels] = (min?-1:1)*res; + (*nrels)++; + } + + /* Well, there may be more in the buffer, but we need no more data. + Ignore anything that is left. */ + return; +} + +void sensors_write_reals(int nrels,void *buffer,int *bufsize,long *results, + int magnitude) +{ + #define BUFLEN 20 + char BUF[BUFLEN+1]; /* An individual representation should fit in here! */ + char printfstr[10]; + int nr=0; + int buflen,mag,times; + int curbufsize=0; + + while ((nr < nrels) && (curbufsize < *bufsize)) { + mag=magnitude; + + if (nr != 0) { + put_user(' ', (char *) buffer); + curbufsize ++; + ((char *) buffer) ++; + } + + /* Fill BUF with the representation of the next string */ + if (mag <= 0) { + + buflen=sprintf(BUF,"%ld",results[nr]); + if (buflen < 0) { /* Oops, a sprintf error! */ + *bufsize=0; + return; + } + while ((mag < 0) && (buflen < BUFLEN)) { + BUF[buflen++]='0'; + mag++; + } + BUF[buflen]=0; + } else { + times=1; + for (times=1; mag-- > 0; times *= 10); + if (results[nr] < 0) { + BUF[0] = '-'; + buflen = 1; + } else + buflen=0; + strcpy(printfstr,"%ld.%0Xld"); + printfstr[6]=magnitude+'0'; + buflen+=sprintf(BUF+buflen,printfstr,abs(results[nr])/times, + abs(results[nr])%times); + if (buflen < 0) { /* Oops, a sprintf error! */ + *bufsize=0; + return; + } + } + + /* Now copy it to the user-space buffer */ + if (buflen + curbufsize > *bufsize) + buflen=*bufsize-curbufsize; + copy_to_user(buffer,BUF,buflen); + curbufsize += buflen; + (char *) buffer += buflen; + + nr ++; + } + if (curbufsize < *bufsize) { + put_user('\n', (char *) buffer); + curbufsize ++; + } + *bufsize=curbufsize; +} + +int sensors_init(void) +{ + printk("sensors.o version %s (%s)\n",LM_VERSION,LM_DATE); + return 0; +} + +int sensors_cleanup(void) +{ + return 0; +} + +#ifdef MODULE + +MODULE_AUTHOR("Frodo Looijaard "); +MODULE_DESCRIPTION("LM78 driver"); + +int init_module(void) +{ + return sensors_init(); +} + +int cleanup_module(void) +{ + return sensors_cleanup(); +} + +#endif /* MODULE */ + diff --git a/src/sensors.h b/src/sensors.h index 0fe6f832..30fa627a 100644 --- a/src/sensors.h +++ b/src/sensors.h @@ -20,8 +20,45 @@ #ifndef SENSORS_SENSORS_H #define SENSORS_SENSORS_H +#include + +#ifdef __KERNEL__ +extern int sensors_register_entry(struct i2c_client *client, + const char *prefix, ctl_table *ctl_template); +extern void sensors_deregister_entry(int id); +extern void sensors_parse_reals(int *nrels, void *buffer, int bufsize, + long *results, int magnitude); +extern void sensors_write_reals(int nrels,void *buffer,int *bufsize, + long *results, int magnitude); + +#endif /* def __KERNEL__ */ + + /* Driver IDs */ #define I2C_DRIVERID_I2CPROC 1001 #define I2C_DRIVERID_LM78 1002 +/* Sysctl IDs */ +#ifdef DEV_HWMON +#define DEV_SENSORS DEV_HWMON +#else /* ndef DEV_HWMOM */ +#define DEV_SENSORS 2 /* The id of the lm_sensors directory within the + dev table */ +#endif /* def DEV_HWMON */ + +#define LM78_SYSCTL_IN0 1000 +#define LM78_SYSCTL_IN1 1001 +#define LM78_SYSCTL_IN2 1002 +#define LM78_SYSCTL_IN3 1003 +#define LM78_SYSCTL_IN4 1004 +#define LM78_SYSCTL_IN5 1005 +#define LM78_SYSCTL_IN6 1006 +#define LM78_SYSCTL_FAN1 1101 +#define LM78_SYSCTL_FAN2 1102 +#define LM78_SYSCTL_FAN3 1103 +#define LM78_SYSCTL_TEMP 1200 +#define LM78_SYSCTL_VID 1300 +#define LM78_SYSCTL_FAN_DIV 2000 +#define LM78_SYSCTL_ALARMS 2001 + #endif /* def SENSORS_SENSORS_H */