#include "ivtv.h"

/* i2c implementation for iTVC15 chip, ivtv project.
 * Author: Kevin Thayer (nufan_wfk at yahoo.com)
 * License: GPL
 * http://www.sourceforge.net/projects/ivtv/
 */


/* moved here from ivtv.h */
static int writeregs(struct i2c_client *client, const unsigned char *regs);
static int attach_inform(struct i2c_client *client);
static int detach_inform(struct i2c_client *client);

int writereg(struct i2c_client *client,
                    unsigned char reg, unsigned char data)
{
        int ret;
        unsigned char msg[] = {0x1f, 0x00};

        printk("<1>writing reg 0x%02x, data 0x%02x\n", reg,data);

        msg[0]=reg; msg[1]=data;
        ret=i2c_master_send(client, msg, 2);
        if (ret!=2)
                printk("writereg error\n");
        return ret;
}

static int writeregs(struct i2c_client *client, const unsigned char *regs)
{
        unsigned char reg, data;

        while (*regs!=0x00) {
                reg =*(regs++);
                data=*(regs++);
                if (writereg(client, reg, data)<0)
                        return -1;
        }
        return 0;
}

static struct i2c_adapter ivtv_i2c_adapter_template = {
	.name = "ivtv i2c driver",
	.id = I2C_HW_B_BT848,	/*algo-bit is OR'd with this*/
	.algo = NULL,		/*set by i2c-algo-bit*/
	.algo_data = NULL, 	/*filled from template*/
	.client_register = attach_inform,
	.client_unregister = detach_inform,
#ifndef NEW_I2C
/* pre i2c-2.8.0 */
	.inc_use = ivtv_i2c_inc,	/*inc usage*/
	.dec_use = ivtv_i2c_dec,	/*dec usage*/
#else
/* i2c-2.8.0 and later */
	.owner = THIS_MODULE,
#endif
};

static struct i2c_algo_bit_data ivtv_i2c_algo_template = {
	NULL,		/*??*/
	ivtv_setsda,	/*setsda function*/
	ivtv_setscl,	/*"*/
	ivtv_getsda,	/*"*/
	ivtv_getscl,	/*"*/
	5,		/*udelay or mdelay*/
       	5, 		/*whatever above isn't*/
	200		/*timeout*/
};

void ivtv_setscl(void *data, int state) {
	struct ivtv *itv = (struct ivtv *)data;

	if (state) 
		itv->i2c_state |= 0x01;
	else
		itv->i2c_state &= ~0x01;

	/* write them out */
	/* write bits are inverted */
	writel(~itv->i2c_state,(itv->reg_mem + IVTV_REG_I2C_SETSCL_OFFSET));
}

void ivtv_setsda(void *data, int state) {
	struct ivtv *itv = (struct ivtv *)data;

	if (state) 
		itv->i2c_state |= 0x01;
	else
		itv->i2c_state &= ~0x01;

	/* write them out */
	/* write bits are inverted */
	writel(~itv->i2c_state,(itv->reg_mem + IVTV_REG_I2C_SETSDA_OFFSET));
}

int ivtv_getscl(void *data) {
	struct ivtv *itv = (struct ivtv *)data;
	return readb(itv->reg_mem + IVTV_REG_I2C_GETSCL_OFFSET);
}

int ivtv_getsda(void *data) {
	struct ivtv *itv = (struct ivtv *)data;
	return readb(itv->reg_mem + IVTV_REG_I2C_GETSDA_OFFSET);
}

static struct i2c_client ivtv_i2c_client_template = {
	name: "ivtv internal use only",
	id:   -1,
};

static int attach_inform(struct i2c_client *client) {
	struct ivtv *itv = (struct ivtv*)client->adapter->data;
        int i; 

	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c client attach\n");
        for (i = 0; i < I2C_CLIENTS_MAX; i++) {
		if (itv->i2c_clients[i] == NULL) {
			itv->i2c_clients[i] = client;
			break;
		}
	}
	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c attach [client=%s,%s]\n",
		client->name, (i < I2C_CLIENTS_MAX) ?  "ok" : "failed");

	return 0;
}

static int detach_inform(struct i2c_client *client) {
	struct ivtv *itv = (struct ivtv*)client->adapter->data;
        int i; 

	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c client detach\n");
        for (i = 0; i < I2C_CLIENTS_MAX; i++) {
		if (itv->i2c_clients[i] == client) {
			itv->i2c_clients[i] = NULL;
			break;
		}
	}
	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c detach [client=%s,%s]\n",
		client->name, (i < I2C_CLIENTS_MAX) ?  "ok" : "failed");

	return 0;
}


void ivtv_call_i2c_client(struct ivtv *itv, int addr, unsigned int cmd, void *arg) {
        int i;

	IVTV_DEBUG(IVTV_DEBUG_I2C, "call_i2c_client\n");
        for (i = 0; i < I2C_CLIENTS_MAX; i++) {
                if (NULL == itv->i2c_clients[i])
                        continue;
                if (NULL == itv->i2c_clients[i]->driver->command)
                        continue;
                if (addr == itv->i2c_clients[i]->addr) {
                	itv->i2c_clients[i]->driver->command(
                        	itv->i2c_clients[i],cmd,arg);
			return;
		}
        }
	IVTV_DEBUG(IVTV_DEBUG_ERR, "i2c client addr: %d not found!\n",addr);
}

int ivtv_i2c_direct(struct ivtv *itv, int addr, const unsigned char *regs) {
        int i, ret=0;

	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c_direct\n");
        for (i = 0; i < I2C_CLIENTS_MAX; i++) {
                if (NULL == itv->i2c_clients[i])
                        continue;
                if (addr == itv->i2c_clients[i]->addr) {
			ret = writeregs(itv->i2c_clients[i], regs);
			break;
		}
        }

	if (ret) {
		IVTV_DEBUG(IVTV_DEBUG_ERR, "error %d writing reg\n", ret);
		return -EIO;
	}

	return 0;
}

#ifndef NEW_I2C
/* pre i2c-2.8.0 */
void ivtv_i2c_inc(struct i2c_adapter *adapter) {
	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c increasing usage count\n");
	MOD_INC_USE_COUNT;
}

void ivtv_i2c_dec(struct i2c_adapter *adapter) {
	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c decreasing usage count\n");
	MOD_DEC_USE_COUNT;
}
#else
/* i2c-2.8.0 and later */
#endif

/* init + register i2c algo-bit adapter */
int __devinit init_ivtv_i2c(struct ivtv *itv) {
	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c init\n");
	memcpy(&itv->i2c_adap, &ivtv_i2c_adapter_template,
               sizeof(struct i2c_adapter));
        memcpy(&itv->i2c_algo, &ivtv_i2c_algo_template,
               sizeof(struct i2c_algo_bit_data));
        memcpy(&itv->i2c_client, &ivtv_i2c_client_template,
               sizeof(struct i2c_client));

        sprintf(itv->i2c_adap.name+strlen(itv->i2c_adap.name),
                " #%d", itv->num);
        itv->i2c_algo.data = itv;
        itv->i2c_adap.data = itv;
        itv->i2c_adap.algo_data = &itv->i2c_algo;
        itv->i2c_client.adapter = &itv->i2c_adap;

	IVTV_DEBUG(IVTV_DEBUG_I2C, "setting scl and sda to 1\n");
        ivtv_setscl(itv,1);
        ivtv_setsda(itv,1);

        itv->i2c_rc = i2c_bit_add_bus(&itv->i2c_adap);
        return itv->i2c_rc;
}

void __devexit exit_ivtv_i2c(struct ivtv *itv) {
	IVTV_DEBUG(IVTV_DEBUG_I2C, "i2c exit\n");

	i2c_bit_del_bus(&itv->i2c_adap);
}

