mirror of
https://github.com/lm-sensors/lm-sensors.git
synced 2026-10-17 10:59:31 +00:00
git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@2445 7894878c-1315-0410-8ee3-d5d059ff63e0
483 lines
13 KiB
C
483 lines
13 KiB
C
/*
|
|
smbus-arp.c - Part of lm_sensors, Linux kernel modules for hardware
|
|
monitoring
|
|
Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
|
|
|
|
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/slab.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/i2c-proc.h>
|
|
#include <linux/init.h>
|
|
#include "version.h"
|
|
|
|
#define DEBUG 1
|
|
|
|
/* So that the module will still compile without i2c-2.6.4;
|
|
if inserted, it will just put out a message and exit in that case */
|
|
#ifdef I2C_CLIENT_PEC
|
|
#define I2C_PEC_SUPPORTED
|
|
#else
|
|
#define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */
|
|
#endif
|
|
|
|
/* Addresses to scan */
|
|
#define ARP_ADDRESS 0x61
|
|
static unsigned short normal_i2c[] = { ARP_ADDRESS, SENSORS_I2C_END };
|
|
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
|
|
static unsigned int normal_isa[] = { SENSORS_ISA_END };
|
|
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
|
|
|
|
/* Insmod parameters */
|
|
SENSORS_INSMOD_1(arp);
|
|
|
|
static int reset;
|
|
MODULE_PARM(reset, "i");
|
|
MODULE_PARM_DESC(reset,
|
|
"Send an ARP Reset Device message to each bus");
|
|
|
|
/* ARP Commands */
|
|
#define ARP_PREPARE 0x01
|
|
#define ARP_RESET_DEV 0x02
|
|
#define ARP_GET_UDID_GEN 0x03
|
|
#define ARP_ASSIGN_ADDR 0x04
|
|
|
|
/* UDID Fields */
|
|
#define ARP_CAPAB 0
|
|
#define ARP_VER 1
|
|
#define ARP_VEND 2
|
|
#define ARP_DEV 4
|
|
#define ARP_INT 6
|
|
#define ARP_SUBVEND 8
|
|
#define ARP_SUBDEV 10
|
|
#define ARP_SPECID 12
|
|
|
|
#define UDID_LENGTH 0x11
|
|
|
|
static u8 reserved[] =
|
|
/* As defined by SMBus Spec. Appendix C */
|
|
{0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x28,
|
|
0x37, ARP_ADDRESS,
|
|
/* As defined by SMBus Spec. Sect. 5.2 */
|
|
0x01, 0x02, 0x03, 0x04, 0x05,
|
|
0x06, 0x07, 0x78, 0x79, 0x7a, 0x7b,
|
|
0x7c, 0x7d, 0x7e, 0x7f,
|
|
/* Common PC addresses (bad idea) */
|
|
0x2d, 0x48, 0x49, /* sensors */
|
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, /* eeproms */
|
|
0x69, /* clock chips */
|
|
/* Must end in 0 which is also reserved */
|
|
0x00};
|
|
|
|
#define SMBUS_ADDRESS_SIZE 0x80
|
|
#define ARP_FREE 0
|
|
#define ARP_RESERVED 1
|
|
#define ARP_BUSY 2
|
|
|
|
#define ARP_MAX_DEVICES 8
|
|
struct arp_device {
|
|
int status;
|
|
u8 udid[UDID_LENGTH];
|
|
u8 dev_cap;
|
|
u8 dev_ver;
|
|
u16 dev_vid;
|
|
u16 dev_did;
|
|
u16 dev_int;
|
|
u16 dev_svid;
|
|
u16 dev_sdid;
|
|
u32 dev_vsid;
|
|
u8 saddr;
|
|
};
|
|
|
|
/* Each client has this additional data */
|
|
struct arp_data {
|
|
struct i2c_client client;
|
|
int sysctl_id;
|
|
|
|
struct semaphore update_lock;
|
|
char valid; /* !=0 if following fields are valid */
|
|
unsigned long last_updated; /* In jiffies */
|
|
|
|
u8 address_pool[SMBUS_ADDRESS_SIZE];
|
|
struct arp_device dev[ARP_MAX_DEVICES];
|
|
};
|
|
|
|
|
|
static int smbusarp_attach_adapter(struct i2c_adapter *adapter);
|
|
static int smbusarp_detect(struct i2c_adapter *adapter, int address,
|
|
unsigned short flags, int kind);
|
|
static int smbusarp_detach_client(struct i2c_client *client);
|
|
|
|
static int smbusarp_init_client(struct i2c_client *client);
|
|
static void smbusarp_contents(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
|
|
static struct i2c_driver smbusarp_driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "SMBUS ARP",
|
|
.id = I2C_DRIVERID_ARP,
|
|
.flags = I2C_DF_NOTIFY,
|
|
.attach_adapter = smbusarp_attach_adapter,
|
|
.detach_client = smbusarp_detach_client,
|
|
};
|
|
|
|
/* -- SENSORS SYSCTL START -- */
|
|
#define ARP_SYSCTL1 1000
|
|
#define ARP_SYSCTL2 1001
|
|
#define ARP_SYSCTL3 1002
|
|
#define ARP_SYSCTL4 1003
|
|
#define ARP_SYSCTL5 1004
|
|
#define ARP_SYSCTL6 1005
|
|
#define ARP_SYSCTL7 1006
|
|
#define ARP_SYSCTL8 1007
|
|
|
|
/* -- SENSORS SYSCTL END -- */
|
|
|
|
static ctl_table smbusarp_dir_table_template[] = {
|
|
{ARP_SYSCTL1, "0", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &smbusarp_contents},
|
|
{ARP_SYSCTL2, "1", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &smbusarp_contents},
|
|
{ARP_SYSCTL3, "2", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &smbusarp_contents},
|
|
{ARP_SYSCTL4, "3", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &smbusarp_contents},
|
|
{ARP_SYSCTL5, "4", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &smbusarp_contents},
|
|
{ARP_SYSCTL6, "5", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &smbusarp_contents},
|
|
{ARP_SYSCTL7, "6", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &smbusarp_contents},
|
|
{ARP_SYSCTL8, "7", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &smbusarp_contents},
|
|
{0}
|
|
};
|
|
|
|
static int smbusarp_id = 0;
|
|
|
|
static int smbusarp_attach_adapter(struct i2c_adapter *adapter)
|
|
{
|
|
return i2c_detect(adapter, &addr_data, smbusarp_detect);
|
|
}
|
|
|
|
/* This function is called by i2c_detect */
|
|
int smbusarp_detect(struct i2c_adapter *adapter, int address,
|
|
unsigned short flags, int kind)
|
|
{
|
|
int i;
|
|
struct i2c_client *new_client;
|
|
struct arp_data *data;
|
|
int err = 0;
|
|
const char *type_name, *client_name;
|
|
|
|
/* must support block data w/ HW PEC or SW PEC */
|
|
if ((!i2c_check_functionality(adapter,
|
|
I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_HWPEC_CALC)) &&
|
|
(!i2c_check_functionality(adapter,
|
|
I2C_FUNC_SMBUS_BLOCK_DATA_PEC)))
|
|
return(0);
|
|
|
|
if (!(data = kmalloc(sizeof(struct arp_data), GFP_KERNEL))) {
|
|
return(-ENOMEM);
|
|
}
|
|
|
|
new_client = &data->client;
|
|
new_client->addr = address;
|
|
new_client->data = data;
|
|
new_client->adapter = adapter;
|
|
new_client->driver = &smbusarp_driver;
|
|
#ifdef I2C_PEC_SUPPORTED
|
|
new_client->flags = I2C_CLIENT_PEC;
|
|
#else
|
|
new_client->flags = 0;
|
|
#endif
|
|
|
|
type_name = "arp";
|
|
client_name = "SMBUS ARP client";
|
|
|
|
strcpy(new_client->name, client_name);
|
|
|
|
new_client->id = smbusarp_id++;
|
|
data->valid = 0;
|
|
init_MUTEX(&data->update_lock);
|
|
|
|
if ((err = i2c_attach_client(new_client)))
|
|
goto ERROR1;
|
|
|
|
if ((i = i2c_register_entry(new_client, type_name,
|
|
smbusarp_dir_table_template)) < 0) {
|
|
err = i;
|
|
goto ERROR4;
|
|
}
|
|
data->sysctl_id = i;
|
|
smbusarp_init_client(new_client);
|
|
return 0;
|
|
|
|
ERROR4:
|
|
i2c_detach_client(new_client);
|
|
ERROR1:
|
|
kfree(data);
|
|
return err;
|
|
}
|
|
|
|
static int smbusarp_detach_client(struct i2c_client *client)
|
|
{
|
|
int err;
|
|
|
|
i2c_deregister_entry(((struct arp_data *) (client->data))->
|
|
sysctl_id);
|
|
|
|
if ((err = i2c_detach_client(client))) {
|
|
printk
|
|
(KERN_ERR "smbus-arp.o: Client deregistration failed, client not detached.\n");
|
|
return err;
|
|
}
|
|
|
|
kfree(client->data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static u8 choose_addr(u8 * pool)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 0x7f; i++) {
|
|
if(pool[i] == ARP_FREE)
|
|
return ((u8) i);
|
|
}
|
|
return 0xff;
|
|
}
|
|
|
|
static int smbusarp_init_client(struct i2c_client *client)
|
|
{
|
|
int ret = -1;
|
|
#ifdef I2C_PEC_SUPPORTED
|
|
struct arp_data *data = client->data;
|
|
u8 blk[I2C_SMBUS_BLOCK_MAX];
|
|
u8 *r;
|
|
u8 addr;
|
|
int i;
|
|
int found = 0;
|
|
int newdev = 0;
|
|
|
|
for(i = 0; i < ARP_MAX_DEVICES; i++)
|
|
data->dev[i].status = ARP_FREE;
|
|
|
|
for(i = 0; i < SMBUS_ADDRESS_SIZE; i++)
|
|
data->address_pool[i] = ARP_FREE;
|
|
|
|
r = reserved;
|
|
do {
|
|
data->address_pool[*r] = ARP_RESERVED;
|
|
} while(*r++);
|
|
|
|
for (i = 0; i < I2C_CLIENT_MAX ; i++)
|
|
if (client->adapter->clients[i])
|
|
data->address_pool[client->adapter->clients[i]->addr] =
|
|
ARP_BUSY;
|
|
|
|
if(reset)
|
|
i2c_smbus_write_byte(client, ARP_RESET_DEV);
|
|
ret = i2c_smbus_write_byte(client, ARP_PREPARE);
|
|
if(ret < 0) {
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG "smbus-arp.o: No ARP response on adapter 0x%X\n", client->adapter->id);
|
|
#endif
|
|
return(-1); /* Packet wasn't acked */
|
|
}
|
|
while(1) {
|
|
ret = i2c_smbus_read_block_data(client, ARP_GET_UDID_GEN, blk);
|
|
if(ret != UDID_LENGTH) {
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG "smbus-arp.o: No/Bad UDID response %d on adapter 0x%X\n", ret, client->adapter->id);
|
|
#endif
|
|
if(found)
|
|
return(found);
|
|
else
|
|
return(-1); /* Bad response */
|
|
}
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG "smbus-arp.o: Good UDID response on adapter 0x%X\n", client->adapter->id);
|
|
printk(KERN_DEBUG " Cap. 0x%02x Rev. 0x%02x Vend. 0x%02x%02x Dev. 0x%02x%02x\n", blk[0], blk[1], blk[2], blk[3], blk[4], blk[5]);
|
|
printk(KERN_DEBUG " Int. 0x%02x%02x Subvend. 0x%02x%02x Subdev. 0x%02x%02x Spec. 0x%02x%02x%02x%02x\n", blk[6], blk[7], blk[8], blk[9], blk[10], blk[11], blk[12], blk[13], blk[14], blk[15]);
|
|
#endif
|
|
/* clean up this... */
|
|
found++;
|
|
do{
|
|
if(data->dev[newdev].status == ARP_FREE)
|
|
break;
|
|
} while(++newdev < ARP_MAX_DEVICES);
|
|
if(newdev == ARP_MAX_DEVICES) {
|
|
printk(KERN_WARNING "smbus-arp.o: No more slots available\n");
|
|
return(-1);
|
|
}
|
|
|
|
/* check device slave addr */
|
|
addr = blk[16];
|
|
if(addr != 0xFF) {
|
|
addr >>= 1;
|
|
if(blk[0] & 0xC0) {
|
|
if(data->address_pool[addr] == ARP_FREE) {
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG " Requested free Non-fixed Address 0x%02x\n", addr);
|
|
#endif
|
|
} else {
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG " Requested busy Non-fixed Address 0x%02x\n", addr);
|
|
#endif
|
|
if((addr =
|
|
choose_addr(data->address_pool)) ==
|
|
0xff) {
|
|
printk(KERN_WARNING "smbus-arp.o: Address pool exhausted\n");
|
|
return(-1);
|
|
}
|
|
}
|
|
} else {
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG " Fixed Address 0x%02x\n", addr);
|
|
#endif
|
|
}
|
|
} else {
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG " No Address\n");
|
|
#endif
|
|
if((addr = choose_addr(data->address_pool)) == 0xff) {
|
|
printk(KERN_WARNING "smbus-arp.o: Address pool exhausted\n");
|
|
return(-1);
|
|
}
|
|
|
|
}
|
|
/* store things both ways */
|
|
for(i = 0; i < UDID_LENGTH; i++)
|
|
data->dev[newdev].udid[i] = blk[i];
|
|
data->dev[newdev].saddr = addr;
|
|
data->dev[newdev].status = ARP_BUSY;
|
|
data->dev[newdev].dev_cap = blk[0];
|
|
data->dev[newdev].dev_ver = blk[1];
|
|
data->dev[newdev].dev_vid = (blk[2] << 8) | blk[3];
|
|
data->dev[newdev].dev_did = (blk[4] << 8) | blk[5];
|
|
data->dev[newdev].dev_int = (blk[6] << 8) | blk[7];
|
|
data->dev[newdev].dev_svid = (blk[8] << 8) | blk[9];
|
|
data->dev[newdev].dev_sdid = (blk[10] << 8) | blk[11];
|
|
data->dev[newdev].dev_vsid = (blk[12] << 24) | (blk[13] << 16) |
|
|
(blk[14] << 8) | blk[15] ;
|
|
|
|
blk[16] = addr << 1;
|
|
ret = i2c_smbus_write_block_data(client, ARP_ASSIGN_ADDR,
|
|
UDID_LENGTH, blk);
|
|
if(ret) {
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG " Bad response, address 0x%02x not assigned\n", addr);
|
|
#endif
|
|
} else {
|
|
data->address_pool[addr] = ARP_BUSY;
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG " Assigned address 0x%02x\n", addr);
|
|
#endif
|
|
}
|
|
/* retry? */
|
|
|
|
} /* while 1 */
|
|
#else
|
|
printk(KERN_WARNING "smbus-arp.o: A client responded to the ARP address but "
|
|
"your kernel does not support SMBus ARP/PEC!\n");
|
|
ret = -EPERM;
|
|
#endif
|
|
return(ret);
|
|
}
|
|
|
|
|
|
/* reassign address on writex */
|
|
void smbusarp_contents(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results)
|
|
{
|
|
int nr = ctl_name - ARP_SYSCTL1;
|
|
struct arp_data *data = client->data;
|
|
int ret;
|
|
u8 save;
|
|
u8 a;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 0;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
if(data->dev[nr].status == ARP_BUSY) {
|
|
results[0] = data->dev[nr].saddr;
|
|
results[1] = data->dev[nr].dev_cap;
|
|
results[2] = data->dev[nr].dev_ver;
|
|
results[3] = data->dev[nr].dev_vid;
|
|
results[4] = data->dev[nr].dev_did;
|
|
results[5] = data->dev[nr].dev_int;
|
|
results[6] = data->dev[nr].dev_svid;
|
|
results[7] = data->dev[nr].dev_sdid;
|
|
results[8] = data->dev[nr].dev_vsid;
|
|
*nrels_mag = 9;
|
|
} else {
|
|
*nrels_mag = 0;
|
|
}
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
a = results[0];
|
|
if(*nrels_mag >= 1 &&
|
|
a < SMBUS_ADDRESS_SIZE &&
|
|
data->dev[nr].status == ARP_BUSY &&
|
|
data->address_pool[a] == ARP_FREE) {
|
|
save = data->dev[nr].udid[16];
|
|
data->dev[nr].udid[16] = a << 1;
|
|
ret = i2c_smbus_write_block_data(client, ARP_ASSIGN_ADDR,
|
|
UDID_LENGTH, data->dev[nr].udid);
|
|
if(ret) {
|
|
data->dev[nr].udid[16] = save;
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG "smbus-arp Bad response, address 0x%02x not assigned\n", a);
|
|
#endif
|
|
} else {
|
|
data->dev[nr].saddr = a;
|
|
data->address_pool[a] = ARP_BUSY;
|
|
#ifdef DEBUG
|
|
printk(KERN_DEBUG "smbus-arp Assigned address 0x%02x\n", a);
|
|
#endif
|
|
}
|
|
} else {
|
|
printk(KERN_WARNING "smbus-arp Bad address 0x%02x\n", a);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int __init sm_smbusarp_init(void)
|
|
{
|
|
printk("smbus-arp.o version %s (%s)\n", LM_VERSION, LM_DATE);
|
|
/* magic force invocation */
|
|
force_arp[0] = -1;
|
|
force_arp[1] = ARP_ADDRESS;
|
|
return i2c_add_driver(&smbusarp_driver);
|
|
}
|
|
|
|
static void __exit sm_smbusarp_exit(void)
|
|
{
|
|
i2c_del_driver(&smbusarp_driver);
|
|
}
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
|
|
MODULE_DESCRIPTION("SMBUS ARP Driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(sm_smbusarp_init);
|
|
module_exit(sm_smbusarp_exit);
|