mirror of
https://github.com/lm-sensors/lm-sensors.git
synced 2026-11-10 16:30:50 +00:00
Supported insmod parameters: ignore, ignore_range probe, probe_range force, force_lm78, force_lm78j, force_lm79 force* overrules ignore* overrules probe* The *_range parameters need three elements for each specification: bus,start_addr,end_addr The address ranges are inclusive. The other parameters need two elements for each specification: bus,addr In each case, '-1' stands for 'any I2C bus', and 9191 stands for 'the ISA bus' (Bonus question: who can figure out why I choose 9191?) In each case, just append if you want several specification, for example: insmod lm78 probe=9191,0x2a0,1,0x56 force_* does no detection, not even chip detection; it blindly assumes you know what you are doing. plain force does the chip detection, but nothing else; but it can still fail if the register read-out does not match a chip type. Detection is done in exactly the same way as sensors-detect, except that only the range 0x20-0x2f is examined by default. This needs to be synchronized somehow with the detect script. I would rather scan the whole I2C address range, but with those clueless PIIX4 hangs when clock chips are read, that would simply give too much trouble. The detect script has slightly better ISA detection now, too. git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@325 7894878c-1315-0410-8ee3-d5d059ff63e0
811 lines
28 KiB
C
811 lines
28 KiB
C
/*
|
|
lm78.c - Part of lm_sensors, Linux kernel modules for hardware
|
|
monitoring
|
|
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
|
|
|
|
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 <linux/module.h>
|
|
#include <linux/malloc.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/sysctl.h>
|
|
#include <asm/errno.h>
|
|
#include <asm/io.h>
|
|
#include <linux/types.h>
|
|
#include "smbus.h"
|
|
#include "version.h"
|
|
#include "i2c-isa.h"
|
|
#include "sensors.h"
|
|
#include "i2c.h"
|
|
#include "compat.h"
|
|
|
|
/* Addresses to scan */
|
|
static unsigned short normal_i2c[] = {SENSORS_I2C_END};
|
|
static unsigned short normal_i2c_range[] = {0x20,0x2f,SENSORS_I2C_END};
|
|
static unsigned int normal_isa[] = {0x0290,SENSORS_ISA_END};
|
|
static unsigned int normal_isa_range[] = {SENSORS_ISA_END};
|
|
|
|
/* Insmod parameters */
|
|
SENSORS_INSMOD_3(lm78,lm78j,lm79);
|
|
/* Many LM78 constants specified 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
|
|
#define LM78_REG_CHIPID 0x49
|
|
#define LM78_REG_I2C_ADDR 0x48
|
|
|
|
|
|
/* Conversions. Rounding is only done on the TO_REG variants. */
|
|
#define IN_TO_REG(val) (((val) * 10 + 8)/16)
|
|
#define IN_FROM_REG(val) (((val) * 16) / 10)
|
|
|
|
#define FAN_TO_REG(val,div) ((val)==0?255:((1350000+(val)*(div)/2)/\
|
|
((val)*(div))) & 0xff)
|
|
#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div)))
|
|
|
|
#define TEMP_TO_REG(val) (((val)<0?(((val)-5)/10)&0xff:((val)+5)/10) & 0xff)
|
|
#define TEMP_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10)
|
|
|
|
#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\
|
|
(val)>=0x06?0:205-(val)*5)
|
|
#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?0:1)
|
|
|
|
/* Initial limits. To keep them sane, we use the 'standard' translation as
|
|
specified in the LM78 sheet. Use the config file to set better limits. */
|
|
#define LM78_INIT_IN_0 (vid==350?280:vid)
|
|
#define LM78_INIT_IN_1 (vid==350?280:vid)
|
|
#define LM78_INIT_IN_2 330
|
|
#define LM78_INIT_IN_3 (((500) * 100)/168)
|
|
#define LM78_INIT_IN_4 (((1200) * 10)/38)
|
|
#define LM78_INIT_IN_5 (((-1200) * -604)/2100)
|
|
#define LM78_INIT_IN_6 (((-500) * -604)/909)
|
|
|
|
#define LM78_INIT_IN_PERCENTAGE 10
|
|
|
|
#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 600
|
|
#define LM78_INIT_TEMP_HYST 500
|
|
|
|
#ifdef MODULE
|
|
extern int init_module(void);
|
|
extern int cleanup_module(void);
|
|
#endif /* MODULE */
|
|
|
|
/* There are some complications in a module like this. First off, LM78 chips
|
|
may be both present on the SMBus and the ISA bus, and we have to handle
|
|
those cases separately at some places. Second, there might be several
|
|
LM78 chips available (well, actually, that is probably never done; but
|
|
it is a clean illustration of how to handle a case like that). Finally,
|
|
a specific chip may be attached to *both* ISA and SMBus, and we would
|
|
not like to detect it double. Fortunately, in the case of the LM78 at
|
|
least, a register tells us what SMBus address we are on, so that helps
|
|
a bit - except if there could be more than one SMBus. Groan. No solution
|
|
for this yet. */
|
|
|
|
/* This module may seem overly long and complicated. In fact, it is not so
|
|
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;
|
|
enum chips type;
|
|
|
|
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[3]; /* Register encoding, shifted right */
|
|
u8 vid; /* Register encoding, combined */
|
|
u16 alarms; /* Register encoding, combined */
|
|
};
|
|
|
|
|
|
static int lm78_init(void);
|
|
static int lm78_cleanup(void);
|
|
|
|
static int lm78_attach_adapter(struct i2c_adapter *adapter);
|
|
static int lm78_detect(struct i2c_adapter *adapter, int address, int kind);
|
|
static int lm78_detach_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 void lm78_in(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results);
|
|
static void lm78_fan(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results);
|
|
static void lm78_temp(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results);
|
|
static void lm78_vid(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results);
|
|
static void lm78_alarms(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results);
|
|
static void lm78_fan_div(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, 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 datastructure takes now. */
|
|
#define MAX_LM78_NR 8
|
|
static struct i2c_client *lm78_list[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 */
|
|
static struct i2c_driver lm78_driver = {
|
|
/* name */ "LM78(-J) and LM79 sensor driver",
|
|
/* id */ I2C_DRIVERID_LM78,
|
|
/* flags */ DF_NOTIFY,
|
|
/* attach_adapter */ &lm78_attach_adapter,
|
|
/* detach_client */ &lm78_detach_client,
|
|
/* command */ &lm78_command,
|
|
/* inc_use */ &lm78_inc_use,
|
|
/* dec_use */ &lm78_dec_use
|
|
};
|
|
|
|
/* 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, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_in },
|
|
{ LM78_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_in },
|
|
{ LM78_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_in },
|
|
{ LM78_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_in },
|
|
{ LM78_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_in },
|
|
{ LM78_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_in },
|
|
{ LM78_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_in },
|
|
{ LM78_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_fan },
|
|
{ LM78_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_fan },
|
|
{ LM78_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_fan },
|
|
{ LM78_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_temp },
|
|
{ LM78_SYSCTL_VID, "vid", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_vid },
|
|
{ LM78_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_fan_div },
|
|
{ LM78_SYSCTL_ALARMS, "alarms", NULL, 0, 0644, NULL, &sensors_proc_real,
|
|
&sensors_sysctl_real, NULL, &lm78_alarms },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
/* This function is called when:
|
|
* 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)
|
|
{
|
|
return sensors_detect(adapter,&addr_data,lm78_detect);
|
|
}
|
|
|
|
/* This function is called by sensors_detect */
|
|
int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
|
|
{
|
|
int i;
|
|
struct i2c_client *new_client;
|
|
struct lm78_data *data;
|
|
int err=0;
|
|
const char *type_name = "";
|
|
const char *client_name = "";
|
|
|
|
|
|
if (kind < 0) {
|
|
/* We need address registration for the I2C bus too. That is not yet
|
|
implemented. */
|
|
if (i2c_is_isa_adapter(adapter)) {
|
|
|
|
#define REALLY_SLOW_IO
|
|
/* We need the timeouts for at least some LM78-like chips. But only
|
|
if we read 'undefined' registers. */
|
|
if (check_region(address,LM78_EXTENT))
|
|
goto ERROR0;
|
|
i = inb_p(address + 1);
|
|
if (inb_p(address + 2) != i)
|
|
goto ERROR0;
|
|
if (inb_p(address + 3) != i)
|
|
goto ERROR0;
|
|
if (inb_p(address + 7) != i)
|
|
goto ERROR0;
|
|
#undef REALLY_SLOW_IO
|
|
|
|
/* Let's just hope nothing breaks here */
|
|
i = inb_p(address + 5) & 0x7f;
|
|
outb_p(~i & 0x7f,address+5);
|
|
if ((inb_p(address + 5) & 0x7f) != (~i & 0x7f)) {
|
|
outb_p(i,address+5);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* OK. For now, we presume we have a valid client. We now create the
|
|
client structure, even though we cannot fill it completely yet.
|
|
But it allows us to access lm78_{read,write}_value. */
|
|
|
|
if (! (new_client = kmalloc((i2c_is_isa_adapter(adapter)?
|
|
sizeof(struct isa_client):
|
|
sizeof(struct i2c_client)) +
|
|
sizeof(struct lm78_data),
|
|
GFP_KERNEL))) {
|
|
err = -ENOMEM;
|
|
goto ERROR0;
|
|
}
|
|
|
|
if (i2c_is_isa_adapter(adapter)) {
|
|
data = (struct lm78_data *) (((struct isa_client *) new_client) + 1);
|
|
new_client->addr = 0;
|
|
((struct isa_client *) new_client)->isa_addr = address;
|
|
data->lock = MUTEX;
|
|
} else {
|
|
data = (struct lm78_data *) (((struct i2c_client *) new_client) + 1);
|
|
new_client->addr = address;
|
|
}
|
|
new_client->data = data;
|
|
new_client->adapter = adapter;
|
|
new_client->driver = &lm78_driver;
|
|
|
|
/* Now, we do the remaining detection. */
|
|
|
|
if (kind < 0) {
|
|
if (lm78_read_value(new_client,LM78_REG_CONFIG) & 0x80)
|
|
goto ERROR1;
|
|
if (!i2c_is_isa_adapter(adapter) &&
|
|
(lm78_read_value(new_client,LM78_REG_I2C_ADDR) != address))
|
|
goto ERROR1;
|
|
}
|
|
|
|
/* Determine the chip type. */
|
|
if (kind <= 0) {
|
|
i = lm78_read_value(new_client,LM78_REG_CHIPID);
|
|
if (i == 0x00) {
|
|
kind = lm78;
|
|
type_name = "lm78";
|
|
client_name = "LM78 chip";
|
|
} else if (i == 0x40) {
|
|
kind = lm78j;
|
|
type_name = "lm78-j";
|
|
client_name = "LM78-J chip";
|
|
} else if ((i & 0xfe) == 0xc0) {
|
|
kind = lm79;
|
|
type_name = "lm79";
|
|
client_name = "LM79 chip";
|
|
} else
|
|
goto ERROR1;
|
|
}
|
|
|
|
/* Reserve the ISA region */
|
|
if i2c_is_isa_adapter(adapter)
|
|
request_region(address, LM78_EXTENT, type_name);
|
|
|
|
/* Fill in the remaining client fields and put it into the global list */
|
|
strcpy(new_client->name,client_name);
|
|
data->type = kind;
|
|
|
|
for(i = 0; i < MAX_LM78_NR; i++)
|
|
if (! lm78_list[i])
|
|
break;
|
|
if (i == MAX_LM78_NR) {
|
|
printk("lm78.o: No empty slots left, recompile and heighten "
|
|
"MAX_LM78_NR!\n");
|
|
err = -ENOMEM;
|
|
goto ERROR2;
|
|
}
|
|
lm78_list[i] = new_client;
|
|
new_client->id = i;
|
|
data->valid = 0;
|
|
data->update_lock = MUTEX;
|
|
|
|
/* Tell the I2C layer a new client has arrived */
|
|
if ((err = i2c_attach_client(new_client)))
|
|
goto ERROR3;
|
|
|
|
/* Register a new directory entry with module sensors */
|
|
if ((i = sensors_register_entry((struct i2c_client *) new_client,
|
|
type_name,
|
|
lm78_dir_table_template)) < 0) {
|
|
err = i;
|
|
goto ERROR4;
|
|
}
|
|
data->sysctl_id = i;
|
|
|
|
/* Initialize the LM78 chip */
|
|
lm78_init_client((struct i2c_client *) new_client);
|
|
return 0;
|
|
|
|
/* OK, this is not exactly good programming practice, usually. But it is
|
|
very code-efficient in this case. */
|
|
|
|
ERROR4:
|
|
i2c_detach_client(new_client);
|
|
ERROR3:
|
|
for (i = 0; i < MAX_LM78_NR; i++)
|
|
if (new_client == lm78_list[i])
|
|
lm78_list[i] = NULL;
|
|
ERROR2:
|
|
if i2c_is_isa_adapter(adapter)
|
|
release_region(address,LM78_EXTENT);
|
|
ERROR1:
|
|
kfree(new_client);
|
|
ERROR0:
|
|
return err;
|
|
}
|
|
|
|
int lm78_detach_client(struct i2c_client *client)
|
|
{
|
|
int err,i;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
lm78_list[i] = NULL;
|
|
|
|
if i2c_is_isa_client(client)
|
|
release_region(((struct isa_client *)client)->isa_addr,LM78_EXTENT);
|
|
kfree(client);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* No commands defined yet */
|
|
int lm78_command(struct i2c_client *client, unsigned int cmd, void *arg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Nothing here yet */
|
|
void lm78_inc_use (struct i2c_client *client)
|
|
{
|
|
#ifdef MODULE
|
|
MOD_INC_USE_COUNT;
|
|
#endif
|
|
}
|
|
|
|
/* Nothing here yet */
|
|
void lm78_dec_use (struct i2c_client *client)
|
|
{
|
|
#ifdef MODULE
|
|
MOD_DEC_USE_COUNT;
|
|
#endif
|
|
}
|
|
|
|
|
|
/* The SMBus locks itself, but ISA access must be locked explicitely!
|
|
We don't want to lock the whole ISA bus, so we lock each client
|
|
separately.
|
|
We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks,
|
|
would slow down the LM78 access and should not be necessary.
|
|
There are some ugly typecasts here, but the good new is - they should
|
|
nowhere else be necessary! */
|
|
int lm78_read_value(struct i2c_client *client, u8 reg)
|
|
{
|
|
int res;
|
|
if (i2c_is_isa_client(client)) {
|
|
down(& (((struct lm78_data *) (client->data)) -> lock));
|
|
outb_p(reg,(((struct isa_client *) client)->isa_addr) +
|
|
LM78_ADDR_REG_OFFSET);
|
|
res = inb_p((((struct isa_client *) client)->isa_addr) +
|
|
LM78_DATA_REG_OFFSET);
|
|
up( & (((struct lm78_data *) (client->data)) -> lock));
|
|
return res;
|
|
} else
|
|
return smbus_read_byte_data(client->adapter,client->addr, reg);
|
|
}
|
|
|
|
/* The SMBus locks itself, but ISA access muse be locked explicitely!
|
|
We don't want to lock the whole ISA bus, so we lock each client
|
|
separately.
|
|
We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks,
|
|
would slow down the LM78 access and should not be necessary.
|
|
There are some ugly typecasts here, but the good new is - they should
|
|
nowhere else be necessary! */
|
|
int lm78_write_value(struct i2c_client *client, u8 reg, u8 value)
|
|
{
|
|
if (i2c_is_isa_client(client)) {
|
|
down(&(((struct lm78_data *) (client->data)) -> lock));
|
|
outb_p(reg,((struct isa_client *) client)->isa_addr + LM78_ADDR_REG_OFFSET);
|
|
outb_p(value,((struct isa_client *) client)->isa_addr + LM78_DATA_REG_OFFSET);
|
|
up(&(((struct lm78_data *) (client->data)) -> lock));
|
|
return 0;
|
|
} else
|
|
return smbus_write_byte_data(client->adapter, client->addr, reg,value);
|
|
}
|
|
|
|
/* Called when we have found a new LM78. It should set limits, etc. */
|
|
void lm78_init_client(struct i2c_client *client)
|
|
{
|
|
int vid;
|
|
|
|
/* 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);
|
|
|
|
vid = lm78_read_value(client,LM78_REG_VID_FANDIV) & 0x0f;
|
|
if (((struct lm78_data *) (client->data))->type == lm79)
|
|
vid |= (lm78_read_value(client,LM78_REG_CHIPID) & 0x01) >> 4;
|
|
else
|
|
vid |= 0x10;
|
|
vid = VID_FROM_REG(vid);
|
|
|
|
lm78_write_value(client,LM78_REG_IN_MIN(0),IN_TO_REG(LM78_INIT_IN_MIN_0));
|
|
lm78_write_value(client,LM78_REG_IN_MAX(0),IN_TO_REG(LM78_INIT_IN_MAX_0));
|
|
lm78_write_value(client,LM78_REG_IN_MIN(1),IN_TO_REG(LM78_INIT_IN_MIN_1));
|
|
lm78_write_value(client,LM78_REG_IN_MAX(1),IN_TO_REG(LM78_INIT_IN_MAX_1));
|
|
lm78_write_value(client,LM78_REG_IN_MIN(2),IN_TO_REG(LM78_INIT_IN_MIN_2));
|
|
lm78_write_value(client,LM78_REG_IN_MAX(2),IN_TO_REG(LM78_INIT_IN_MAX_2));
|
|
lm78_write_value(client,LM78_REG_IN_MIN(3),IN_TO_REG(LM78_INIT_IN_MIN_3));
|
|
lm78_write_value(client,LM78_REG_IN_MAX(3),IN_TO_REG(LM78_INIT_IN_MAX_3));
|
|
lm78_write_value(client,LM78_REG_IN_MIN(4),IN_TO_REG(LM78_INIT_IN_MIN_4));
|
|
lm78_write_value(client,LM78_REG_IN_MAX(4),IN_TO_REG(LM78_INIT_IN_MAX_4));
|
|
lm78_write_value(client,LM78_REG_IN_MIN(5),IN_TO_REG(LM78_INIT_IN_MIN_5));
|
|
lm78_write_value(client,LM78_REG_IN_MAX(5),IN_TO_REG(LM78_INIT_IN_MAX_5));
|
|
lm78_write_value(client,LM78_REG_IN_MIN(6),IN_TO_REG(LM78_INIT_IN_MIN_6));
|
|
lm78_write_value(client,LM78_REG_IN_MAX(6),IN_TO_REG(LM78_INIT_IN_MAX_6));
|
|
lm78_write_value(client,LM78_REG_FAN_MIN(1),
|
|
FAN_TO_REG(LM78_INIT_FAN_MIN_1,2));
|
|
lm78_write_value(client,LM78_REG_FAN_MIN(2),
|
|
FAN_TO_REG(LM78_INIT_FAN_MIN_2,2));
|
|
lm78_write_value(client,LM78_REG_FAN_MIN(3),
|
|
FAN_TO_REG(LM78_INIT_FAN_MIN_3,2));
|
|
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);
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
#ifdef DEBUG
|
|
printk("Starting lm78 update\n");
|
|
#endif
|
|
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;
|
|
if (data->type == lm79)
|
|
data->vid |= (lm78_read_value(client,LM78_REG_CHIPID) & 0x01) >> 4;
|
|
else
|
|
data->vid |= 0x10;
|
|
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;
|
|
|
|
data->fan_div[2] = 1;
|
|
}
|
|
|
|
up(&data->update_lock);
|
|
}
|
|
|
|
|
|
/* The next few functions are the call-back functions of the /proc/sys and
|
|
sysctl files. Which function is used is defined in the ctl_table in
|
|
the extra1 field.
|
|
Each function must return the magnitude (power of 10 to divide the date
|
|
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
|
|
put a maximum of *nrels elements in results reflecting the data of this
|
|
file, and set *nrels to the number it actually put in it, if operation==
|
|
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
|
|
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
|
|
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
|
|
large enough (by checking the incoming value of *nrels). This is not very
|
|
good practice, but as long as you put less than about 5 values in results,
|
|
you can assume it is large enough. */
|
|
void lm78_in(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct lm78_data *data = client->data;
|
|
int nr = ctl_name - LM78_SYSCTL_IN0;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 2;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
lm78_update_client(client);
|
|
results[0] = IN_FROM_REG(data->in_min[nr]);
|
|
results[1] = IN_FROM_REG(data->in_max[nr]);
|
|
results[2] = IN_FROM_REG(data->in[nr]);
|
|
*nrels_mag = 3;
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
if (*nrels_mag >= 1) {
|
|
data->in_min[nr] = IN_TO_REG(results[0]);
|
|
lm78_write_value(client,LM78_REG_IN_MIN(nr),data->in_min[nr]);
|
|
}
|
|
if (*nrels_mag >= 2) {
|
|
data->in_max[nr] = IN_TO_REG(results[1]);
|
|
lm78_write_value(client,LM78_REG_IN_MAX(nr),data->in_max[nr]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void lm78_fan(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct lm78_data *data = client->data;
|
|
int nr = ctl_name - LM78_SYSCTL_FAN1 + 1;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 0;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
lm78_update_client(client);
|
|
results[0] = FAN_FROM_REG(data->fan_min[nr-1],
|
|
DIV_FROM_REG(data->fan_div[nr-1]));
|
|
results[1] = FAN_FROM_REG(data->fan[nr-1],
|
|
DIV_FROM_REG(data->fan_div[nr-1]));
|
|
*nrels_mag = 2;
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
if (*nrels_mag >= 1) {
|
|
data->fan_min[nr-1] = FAN_TO_REG(results[0],
|
|
DIV_FROM_REG(data->fan_div[nr-1]));
|
|
lm78_write_value(client,LM78_REG_FAN_MIN(nr),data->fan_min[nr-1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void lm78_temp(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct lm78_data *data = client->data;
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 1;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
lm78_update_client(client);
|
|
results[0] = TEMP_FROM_REG(data->temp_over);
|
|
results[1] = TEMP_FROM_REG(data->temp_hyst);
|
|
results[2] = TEMP_FROM_REG(data->temp);
|
|
*nrels_mag = 3;
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
if (*nrels_mag >= 1) {
|
|
data->temp_over = TEMP_TO_REG(results[0]);
|
|
lm78_write_value(client,LM78_REG_TEMP_OVER,data->temp_over);
|
|
}
|
|
if (*nrels_mag >= 2) {
|
|
data->temp_hyst = TEMP_TO_REG(results[1]);
|
|
lm78_write_value(client,LM78_REG_TEMP_HYST,data->temp_hyst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void lm78_vid(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct lm78_data *data = client->data;
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 2;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
lm78_update_client(client);
|
|
results[0] = VID_FROM_REG(data->vid);
|
|
*nrels_mag = 1;
|
|
}
|
|
}
|
|
|
|
void lm78_alarms(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct lm78_data *data = client->data;
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 0;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
lm78_update_client(client);
|
|
results[0] = ALARMS_FROM_REG(data->alarms);
|
|
*nrels_mag = 1;
|
|
}
|
|
}
|
|
|
|
void lm78_fan_div(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct lm78_data *data = client->data;
|
|
int old;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 0;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
lm78_update_client(client);
|
|
results[0] = DIV_FROM_REG(data->fan_div[0]);
|
|
results[1] = DIV_FROM_REG(data->fan_div[1]);
|
|
results[2] = 2;
|
|
*nrels_mag = 3;
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
old = lm78_read_value(client,LM78_REG_VID_FANDIV);
|
|
if (*nrels_mag >= 2) {
|
|
data->fan_div[1] = DIV_TO_REG(results[1]);
|
|
old = (old & 0x3f) | (data->fan_div[1] << 6);
|
|
}
|
|
if (*nrels_mag >= 1) {
|
|
data->fan_div[0] = DIV_TO_REG(results[0]);
|
|
old = (old & 0xcf) | (data->fan_div[0] << 4);
|
|
lm78_write_value(client,LM78_REG_VID_FANDIV,old);
|
|
}
|
|
}
|
|
}
|
|
|
|
int lm78_init(void)
|
|
{
|
|
int res;
|
|
|
|
printk("lm78.o version %s (%s)\n",LM_VERSION,LM_DATE);
|
|
lm78_initialized = 0;
|
|
|
|
if ((res =i2c_add_driver(&lm78_driver))) {
|
|
printk("lm78.o: Driver registration failed, module not inserted.\n");
|
|
lm78_cleanup();
|
|
return res;
|
|
}
|
|
lm78_initialized ++;
|
|
return 0;
|
|
}
|
|
|
|
int lm78_cleanup(void)
|
|
{
|
|
int res;
|
|
|
|
if (lm78_initialized >= 1) {
|
|
if ((res = i2c_del_driver(&lm78_driver))) {
|
|
printk("lm78.o: Driver deregistration failed, module not removed.\n");
|
|
return res;
|
|
}
|
|
lm78_initialized --;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef MODULE
|
|
|
|
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
|
|
MODULE_DESCRIPTION("LM78, LM78-J and LM79 driver");
|
|
|
|
int init_module(void)
|
|
{
|
|
return lm78_init();
|
|
}
|
|
|
|
int cleanup_module(void)
|
|
{
|
|
return lm78_cleanup();
|
|
}
|
|
|
|
#endif /* MODULE */
|
|
|