Files
lm-sensors/kernel/chips/smbus-arp.c
T
Jean Delvare c12c82c13d Backport SMBus PEC support rewrite from Linux 2.6:
Discard I2C_FUNC_SMBUS_*_PEC defines. i2c clients are not supposed to
check for PEC support of i2c bus drivers on individual SMBus
transactions, and i2c bus drivers are not supposed to advertise them.


git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@3176 7894878c-1315-0410-8ee3-d5d059ff63e0
2005-11-05 12:24:30 +00:00

479 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 = {
.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_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 | I2C_FUNC_I2C)))
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);
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,
THIS_MODULE)) < 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);