/*
    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);
