/* The Makefile defines this when built with kernel */
#if defined(OUTSIDE_KERNEL)
	#if !defined(EXPORT_SYMTAB)
	       #define EXPORT_SYMTAB
	#endif
#endif

#include <linux/config.h>

/* Begin module versioning code */
#if defined(OUTSIDE_KERNEL)
	#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
 	      #define MODVERSIONS
	#endif
#endif

#include <linux/version.h>

#if defined(OUTSIDE_KERNEL)
	#ifdef MODVERSIONS
		#include <linux/modversions.h>
	#endif
#endif
/* End module versioning code */

#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/version.h>

/* This i2c interface doesn't exist in 2.2.x kernels... */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 0)
#error "i2c is not supported on 2.2 kernels"
#endif

#include <linux/init.h>
#include <asm/uaccess.h>
#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/i2c.h>
#include "i2c-algo-usb.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 10)
MODULE_LICENSE("GPL");
#endif

static inline int 
try_write_address(struct i2c_adapter *i2c_adap, unsigned char addr, int retries)
{
	struct i2c_algo_usb_data *adap = i2c_adap->algo_data;
	int i, ret = -1;
	char buf[4];

	buf[0] = 0x00;
	for (i = 0; i <= retries; i++) {
		ret = (adap->outb(i2c_adap->data, addr, buf, 1));
		// ret = i2c_outb(adap, addr, 0);
		if (ret == 1)
			break;	/* success! */
		udelay(5 /*adap->udelay */ );
		if (i == retries)	/* no success */
			break;
		udelay(adap->udelay);
	}
	if (i)
		printk("i2c-algo-usb.o: needed %d retries for %#2x\n",
		       i, addr);
	return ret;
}

static inline int 
try_read_address(struct i2c_adapter *i2c_adap, unsigned char addr, int retries)
{
	struct i2c_algo_usb_data *adap = i2c_adap->algo_data;
	int i, ret = -1;
	char buf[4];

	for (i = 0; i <= retries; i++) {
		ret = (adap->inb(i2c_adap->data, addr, buf, 1));
		// ret = i2c_outb(adap, addr, 0);
		if (ret == 1)
			break;	/* success! */
		udelay(5 /*adap->udelay */ );
		if (i == retries)	/* no success */
			break;
		udelay(adap->udelay);
	}
	if (i)
		printk("i2c-algo-usb.o: needed %d retries for %#2x\n",
		       i, addr);
	return ret;
}

static inline int 
usb_find_address(struct i2c_adapter *i2c_adap,
		 struct i2c_msg *msg, int retries, unsigned char *add)
{
	unsigned short flags = msg->flags;
	//struct i2c_algo_usb_data *adap = i2c_adap->algo_data;

	unsigned char addr;
	int ret;
	if ((flags & I2C_M_TEN)) {
		/* a ten bit address */
		addr = 0xf0 | ((msg->addr >> 7) & 0x03);
		/* try extended address code... */
		ret = try_write_address(i2c_adap, addr, retries);
		if (ret != 1) {
			printk("died at extended address code.\n");
			return -EREMOTEIO;
		}
		add[0] = addr;
		if (flags & I2C_M_RD) {
			/* okay, now switch into reading mode */
			addr |= 0x01;
			ret = try_read_address(i2c_adap, addr, retries);
			if (ret != 1) {
				printk("died at extended address code.\n");
				return -EREMOTEIO;
			}
		}

	} else {		/* normal 7bit address  */
		addr = (msg->addr << 1);
		if (flags & I2C_M_RD)
			addr |= 1;
		if (flags & I2C_M_REV_DIR_ADDR)
			addr ^= 1;

		add[0] = addr;
		if (flags & I2C_M_RD)
			ret = try_read_address(i2c_adap, addr, retries);
		else
			ret = try_write_address(i2c_adap, addr, retries);

		if (ret != 1) {
			return -EREMOTEIO;
		}
	}
	return 0;
}

static int
usb_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num)
{
	struct i2c_msg *pmsg;
	struct i2c_algo_usb_data *adap = i2c_adap->algo_data;

	int i, ret;
	unsigned char addr;

	for (i = 0; i < num; i++) {
		pmsg = &msgs[i];
		ret = usb_find_address(i2c_adap, pmsg, i2c_adap->retries,
				       &addr);
		if (ret != 0) {
			printk
			    ("i2c-algo-usb: NAK from device adr %#2x msg #%d\n",
			     msgs[i].addr, i);
			return (ret < 0) ? ret : -EREMOTEIO;
		}

		if (pmsg->flags & I2C_M_RD) {
			/* read bytes into buffer */
			ret = (adap->inb(i2c_adap->data, addr, pmsg->buf,
					 pmsg->len));
			if (ret < pmsg->len) {
				return (ret < 0) ? ret : -EREMOTEIO;
			}
		} else {
			/* write bytes from buffer */
			ret = (adap->outb(i2c_adap->data, addr, pmsg->buf,
					  pmsg->len));
			if (ret < pmsg->len) {
				return (ret < 0) ? ret : -EREMOTEIO;
			}
		}
	}
	return num;
}

static int 
algo_control(struct i2c_adapter *adapter, unsigned int cmd, unsigned long arg)
{
	return 0;
}

static u32 
usb_func(struct i2c_adapter *adap)
{
	return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR |
	    I2C_FUNC_PROTOCOL_MANGLING;
}

/* -----exported algorithm data: -------------------------------------	*/

static struct i2c_algorithm i2c_usb_algo = {
	"USB algorithm",
	I2C_ALGO_BIT,		/* FIXME */
	usb_xfer,
	NULL,
	NULL,			/* slave_xmit           */
	NULL,			/* slave_recv           */
	algo_control,		/* ioctl                */
	usb_func,		/* functionality        */
};

/* 
 * registering functions to load algorithms at runtime 
 */
int 
i2c_usb_add_bus(struct i2c_adapter *adap)
{
	printk("i2c-algo-usb.o: hw routines for %s registered.\n",
	       adap->name);

	/* register new adapter to i2c module... */

	adap->id |= i2c_usb_algo.id;
	adap->algo = &i2c_usb_algo;

	adap->timeout = 100;	/* default values, should       */
	adap->retries = 3;	/* be replaced by defines       */

#ifdef MODULE
	MOD_INC_USE_COUNT;
#endif
	i2c_add_adapter(adap);

	return 0;
}


int 
i2c_usb_del_bus(struct i2c_adapter *adap)
{

	i2c_del_adapter(adap);

	printk("i2c-algo-usb.o: adapter unregistered: %s\n", adap->name);

#ifdef MODULE
	MOD_DEC_USE_COUNT;
#endif
	return 0;
}

static int __init 
i2c_algo_usb_init(void)
{
	printk("i2c-algo-usb.o: i2c usb algorithm module\n");
	return 0;
}

static void __exit 
i2c_algo_usb_exit(void)
{
	printk("i2c-algo-usb.o: module unloaded\n");
}

module_init(i2c_algo_usb_init);
module_exit(i2c_algo_usb_exit);

EXPORT_SYMBOL(i2c_usb_add_bus);
EXPORT_SYMBOL(i2c_usb_del_bus);
