#include <linux/config.h>

#if defined(OUTSIDE_KERNEL)
	#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
		#define MODVERSIONS
	#endif

	#include <linux/version.h>

	#ifdef MODVERSIONS
		#include <linux/modversions.h>
	#endif
#else
	#include <linux/version.h>
#endif

#include <linux/module.h>
#include <linux/kernel.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 <linux/errno.h>
#include <linux/i2c.h>
#include "i2c-algo-sccb.h"

#if defined(MODULE_LICENSE)	/* Introduced in ~2.4.10 */
MODULE_LICENSE("GPL");
#endif

static int
sccb_write(struct i2c_adapter *i2c_adap, unsigned char addr, char *buf,
	   short len)
{
	struct i2c_algo_sccb_data *ad = i2c_adap->algo_data;
	void *data = i2c_adap->data;
	int rc, i;

	if (len == 0) {
		printk("i2c-algo-sccb: 0-byte write at address 0x%02X\n", addr);
		/* Address already validated, so succeed unconditionally */
		return 0;
	}

	down(&ad->bus_lock);

	/* Set new address */
	rc = ad->set_addr(data, addr);
	if (rc < 0) goto out;

	if (len == 1) {
		rc = ad->write2(data, *buf);
			if (rc < 0)
				goto out;
	} else if (len >= 2 && (len%2 == 0)) {
		for (i = 0; i < len; i += 2)
			rc = ad->write3(data, *(buf+i), *(buf+i+1));
				if (rc < 0)
					goto out;
	} else {
		printk("i2c-algo-sccb.o: Cannot write %d bytes", len);
		rc = -EINVAL;
		goto out;
	}

out:
	if (rc < 0)
		printk("i2c-algo-sccb.o: I2C write error (%d)\n", rc);

	/* Restore primary IDs */
	if ((ad->set_addr(data, ad->primary_addr)) < 0)
		printk("i2c-algo-sccb.o: Couldn't restore primary I2C slave\n");

	up(&ad->bus_lock);
	return rc;
}

static int
sccb_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num)
{
	struct i2c_msg *pmsg;
	struct i2c_algo_sccb_data *ad = i2c_adap->algo_data;

	int i, ret;
	unsigned char addr;

	for (i = 0; i < num; i++) {
		pmsg = &msgs[i];

		/* Fix up address */
		addr = (pmsg->addr << 1);
		if (pmsg->flags & I2C_M_RD)
			addr |= 1;
		if (pmsg->flags & I2C_M_REV_DIR_ADDR)
			addr ^= 1;

		ret = ad->validate_addr(i2c_adap->data, addr);
		if (ret != 0) {
			printk("i2c-algo-sccb: Adapter rejected addr 0x%02X\n",
			       addr);
			return (ret < 0) ? ret : -EREMOTEIO;
		}

		if (pmsg->flags & I2C_M_RD) {
			printk("i2c-algo-sccb: Warning: read not implemented\n");
			return 0;
		} else {
			/* write bytes from buffer */
			ret = sccb_write(i2c_adap, addr, pmsg->buf, pmsg->len);
			if (ret < 0)
				return ret;
		}
	}
	return num;
}

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

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

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

static struct i2c_algorithm i2c_sccb_algo = {
	"SCCB algorithm",
	I2C_ALGO_BIT,		/* FIXME */
	sccb_xfer,
	NULL,
	NULL,			/* slave_xmit           */
	NULL,			/* slave_recv           */
	algo_control,		/* ioctl                */
	sccb_func,		/* functionality        */
};

/* 
 * registering functions to load algorithms at runtime 
 */
int 
i2c_sccb_add_bus(struct i2c_adapter *adap)
{
	struct i2c_algo_sccb_data *ad;

	printk("i2c-algo-sccb.o: hw routines for %s registered.\n",
	       adap->name);

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

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

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

	ad = adap->algo_data;
	init_MUTEX(&ad->bus_lock);

	MOD_INC_USE_COUNT;

	i2c_add_adapter(adap);

	return 0;
}

int 
i2c_sccb_del_bus(struct i2c_adapter *adap)
{
	i2c_del_adapter(adap);

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

	MOD_DEC_USE_COUNT;

	return 0;
}

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

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

module_init(i2c_algo_sccb_init);
module_exit(i2c_algo_sccb_exit);

EXPORT_SYMBOL(i2c_sccb_add_bus);
EXPORT_SYMBOL(i2c_sccb_del_bus);
