/*
 *
 *  $Id: pvrusb2-i2c-core.c,v 1.5 2006/01/23 06:58:06 mcisely Exp $
 *
 *  Copyright (C) 2005 Mike Isely <isely@pobox.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
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "compat.h"
#include "pvrusb2-i2c-core.h"
#include "pvrusb2-hdw-internal.h"
#include "pvrusb2-debug.h"

#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__)

/*

  This module attempts to implement a compliant I2C adapter for the pvrusb2
  device.  By doing this we can then make use of existing functionality in
  V4L (e.g. tuner.c) rather than rolling our own.

*/

static unsigned int i2c_scan = 0;
module_param(i2c_scan, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time");

static int pvr2_i2c_write(struct pvr2_hdw *hdw, /* Context */
                          u8 i2c_addr,      /* I2C address we're talking to */
                          u8 *data,         /* Data to write */
                          u16 length)       /* Size of data to write */
{
	/* Return value - default 0 means success */
	int ret;

#ifdef notdef
	trace_i2c("pvr2_i2c_write");
#endif

	if (!data) length = 0;
	if (length > (sizeof(hdw->cmd_buffer) - 3)) return -ENOTSUPP;

	LOCK_TAKE(hdw->ctl_lock);

	/* Clear the command buffer (likely to be paranoia) */
	memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer));

	/* Set up command buffer for an I2C write */
	hdw->cmd_buffer[0] = 0x08;      /* write prefix */
	hdw->cmd_buffer[1] = i2c_addr;  /* i2c addr of chip */
	hdw->cmd_buffer[2] = length;    /* length of what follows */
	if (length) memcpy(hdw->cmd_buffer + 3, data, length);

	/* Do the operation */
	ret = pvr2_send_request(hdw,
				hdw->cmd_buffer,
				length + 3,
				hdw->cmd_buffer,
				1);
	if (!ret) {
		if (hdw->cmd_buffer[0] != 8) {
			ret = -EIO;
			if (hdw->cmd_buffer[0] != 7) {
				trace_i2c("unexpected status"
					  " from i2_write[%d]: %d",
					  i2c_addr,hdw->cmd_buffer[0]);
			}
		}
	}
#ifdef notdef
	trace_i2c("i2c_write(%d) len=%d ret=%d stat=%d",i2c_addr,length,ret,
		  hdw->cmd_buffer[0]);
#endif

	LOCK_GIVE(hdw->ctl_lock);

	return ret;
}

static int pvr2_i2c_read(struct pvr2_hdw *hdw, /* Context */
                         u8 i2c_addr,       /* I2C address we're talking to */
                         u8 *data,          /* Data to write */
                         u16 dlen,          /* Size of data to write */
                         u8 *res,           /* Where to put data we read */
                         u16 rlen)          /* Amount of data to read */
{
	/* Return value - default 0 means success */
	int ret;

#ifdef notdef
	trace_i2c("pvr2_i2c_read");
#endif

	if (!data) dlen = 0;
	if (dlen > (sizeof(hdw->cmd_buffer) - 4)) return -ENOTSUPP;
	if (res && (rlen > (sizeof(hdw->cmd_buffer) - 1))) return -ENOTSUPP;

	LOCK_TAKE(hdw->ctl_lock);

	/* Clear the command buffer (likely to be paranoia) */
	memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer));

	/* Set up command buffer for an I2C write followed by a read */
	hdw->cmd_buffer[0] = 0x09;  /* read prefix */
	hdw->cmd_buffer[1] = dlen;  /* arg length */
	hdw->cmd_buffer[2] = rlen;  /* answer length. Device will send one
				       more byte (status). */
	hdw->cmd_buffer[3] = i2c_addr;  /* i2c addr of chip */
	if (dlen) memcpy(hdw->cmd_buffer + 4, data, dlen);

	/* Do the operation */
	ret = pvr2_send_request(hdw,
				hdw->cmd_buffer,
				4 + dlen,
				hdw->cmd_buffer,
				rlen + 1);
	if (!ret) {
		if (hdw->cmd_buffer[0] != 8) {
			ret = -EIO;
			if (hdw->cmd_buffer[0] != 7) {
				trace_i2c("unexpected status"
					  " from i2_read[%d]: %d",
					  i2c_addr,hdw->cmd_buffer[0]);
			}
		}
	}

#ifdef notdef
	trace_i2c("i2c_read(%d) wlen=%d rlen=%d ret=%d stat=%d",
		  i2c_addr,dlen,rlen,ret,hdw->cmd_buffer[0]);
#endif
	/* Copy back the result */
	if (res && rlen) {
		if (ret) {
			/* Error, just blank out the return buffer */
			memset(res, 0, rlen);
		} else {
			memcpy(res, hdw->cmd_buffer + 1, rlen);
		}
	}

	LOCK_GIVE(hdw->ctl_lock);

	return ret;
}

/* This is a very, very limited I2C adapter implementation.  We can only
   support what we actually know will work on the device... */
static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
                         struct i2c_msg msgs[],
                         int num)
{
	int ret = -ENOTSUPP;
	struct pvr2_hdw *hdw = (struct pvr2_hdw *)(i2c_adap->algo_data);

	if ((msgs[0].flags & I2C_M_NOSTART)) {
		trace_i2c("i2c refusing I2C_M_NOSTART");
		goto done;
	}

	if (num == 1) {
		if (msgs[0].flags & I2C_M_RD) {
			/* Simple read */
			u16 tcnt,bcnt,offs;
			if (!msgs[0].len) {
				/* Length == 0 read.  This is a probe. */
				if (pvr2_i2c_read(hdw,msgs[0].addr,
						  0,0,0,0)) {
					ret = -EIO;
					goto done;
				}
				ret = 1;
				goto done;
			}
			/* If the read is short enough we'll do the whole
			   thing atomically.  Otherwise we have no choice
			   but to break apart the reads. */
			tcnt = msgs[0].len;
			offs = 0;
			while (tcnt) {
				bcnt = tcnt;
				if (bcnt > sizeof(hdw->cmd_buffer)-1) {
					bcnt = sizeof(hdw->cmd_buffer)-1;
				}
				if (pvr2_i2c_read(hdw,msgs[0].addr,
						  0,0,
						  msgs[0].buf+offs,bcnt)) {
					ret = -EIO;
					goto done;
				}
				offs += bcnt;
				tcnt -= bcnt;
			}
			ret = 1;
			goto done;
		} else {
			/* Simple write */
			ret = 1;
			if (pvr2_i2c_write(hdw,msgs[0].addr,
					   msgs[0].buf,msgs[0].len)) {
				ret = -EIO;
			}
			goto done;
		}
	} else if (num == 2) {
		if ((!((msgs[0].flags & I2C_M_RD))) &&
		    (msgs[1].flags & I2C_M_RD)) {
			u16 tcnt,bcnt,wcnt,offs;
			/* Write followed by atomic read.  If the read
			   portion is short enough we'll do the whole thing
			   atomically.  Otherwise we have no choice but to
			   break apart the reads. */
			tcnt = msgs[1].len;
			wcnt = msgs[0].len;
			offs = 0;
			while (tcnt || wcnt) {
				bcnt = tcnt;
				if (bcnt > sizeof(hdw->cmd_buffer)-1) {
					bcnt = sizeof(hdw->cmd_buffer)-1;
				}
				if (pvr2_i2c_read(hdw,msgs[0].addr,
						  msgs[0].buf,wcnt,
						  msgs[1].buf+offs,bcnt)) {
					ret = -EIO;
					goto done;
				}
				offs += bcnt;
				tcnt -= bcnt;
				wcnt = 0;
			}
			ret = 2;
			goto done;
		} else {
			trace_i2c("i2c refusing complex transfer"
				  " read0=%d read1=%d",
				  (msgs[0].flags & I2C_M_RD),
				  (msgs[1].flags & I2C_M_RD));
		}
	} else {
		trace_i2c("i2c refusing %d phase transfer",num);
	}

 done:
	if (pvrusb2_debug & PVR2_TRACE_I2C_TRAF) {
		unsigned int idx;
		for (idx = 0; idx < num; idx++) {
			printk(KERN_INFO
			       "pvrusb2 i2c xfer %u/%u:"
			       " addr=0x%x len=%d %s%s",
			       idx+1,num,
			       msgs[idx].addr,
			       msgs[idx].len,
			       (msgs[idx].flags & I2C_M_RD ?
				"read" : "write"),
			       (msgs[idx].flags & I2C_M_NOSTART ?
				" nostart" : ""));
			if (idx+1 == num) {
				printk(" result=%d",ret);
			}
			printk("\n");
		}
		if (!num) {
			printk(KERN_INFO
			       "pvrusb2 i2c xfer null transfer result=%d\n",
			       ret);
		}
	}
	return ret;
}

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

static u32 pvr2_i2c_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_SMBUS_EMUL;
}

static int pvr2_i2c_core_singleton(struct i2c_client *cp,
				   unsigned int cmd,void *arg)
{
	int stat;
	if (!cp) return -EINVAL;
	if (!(cp->driver)) return -EINVAL;
	if (!(cp->driver->command)) return -EINVAL;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16)
	if (!try_module_get(cp->driver->owner)) return -EAGAIN;
#else
	if (!try_module_get(cp->driver->driver.owner)) return -EAGAIN;
#endif
	stat = cp->driver->command(cp,cmd,arg);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16)
	module_put(cp->driver->owner);
#else
	module_put(cp->driver->driver.owner);
#endif
	return stat;
}

int pvr2_i2c_client_cmd(struct pvr2_i2c_client *cp,unsigned int cmd,void *arg)
{
	if (pvrusb2_debug & PVR2_TRACE_I2C_CMD) {
		char buf[100];
		unsigned int cnt;
		cnt = pvr2_i2c_client_describe(cp,PVR2_I2C_DETAIL_DEBUG,
					       buf,sizeof(buf));
		pvr2_trace(PVR2_TRACE_I2C_CMD,
			   "i2c COMMAND to %.*s",cnt,buf);
	}
	return pvr2_i2c_core_singleton(cp->client,cmd,arg);
}

int pvr2_i2c_core_cmd(struct pvr2_hdw *hdw,unsigned int cmd,void *arg)
{
	struct list_head *item,*nc;
	struct pvr2_i2c_client *cp;
	int stat = -EINVAL;

	if (!hdw) return stat;

	mutex_lock(&hdw->i2c_list_lock);
	list_for_each_safe(item,nc,&hdw->i2c_clients) {
		cp = list_entry(item,struct pvr2_i2c_client,list);
		if (!cp->recv_enable) continue;
		mutex_unlock(&hdw->i2c_list_lock);
		stat = pvr2_i2c_client_cmd(cp,cmd,arg);
		mutex_lock(&hdw->i2c_list_lock);
	}
	mutex_unlock(&hdw->i2c_list_lock);
	return stat;
}


static int handler_check(struct pvr2_i2c_client *cp)
{
	struct pvr2_i2c_handler *hp = cp->handler;
	if (!hp) return 0;
	if (!hp->func_table->check) return 0;
	return hp->func_table->check(hp->func_data) != 0;
}

#define BUFSIZE 500

void pvr2_i2c_core_sync(struct pvr2_hdw *hdw)
{
	unsigned long msk;
	unsigned int idx;
	struct list_head *item,*nc;
	struct pvr2_i2c_client *cp;

	if (!hdw->i2c_linked) return;
	if (!(hdw->i2c_pend_types & PVR2_I2C_PEND_ALL)) {
		return;
	}
	mutex_lock(&hdw->i2c_list_lock); do {
		pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: core_sync BEGIN");
		if (hdw->i2c_pend_types & PVR2_I2C_PEND_DETECT) {
			/* One or more I2C clients have attached since we
			   last synced.  So scan the list and identify the
			   new clients. */
			char *buf;
			unsigned int cnt;
			unsigned long amask = 0;
			buf = kmalloc(BUFSIZE,GFP_KERNEL);
			pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: PEND_DETECT");
			hdw->i2c_pend_types &= ~PVR2_I2C_PEND_DETECT;
			list_for_each(item,&hdw->i2c_clients) {
				cp = list_entry(item,struct pvr2_i2c_client,
						list);
				if (!cp->detected_flag) {
					cp->ctl_mask = 0;
					pvr2_i2c_probe(hdw,cp);
					cp->detected_flag = !0;
					msk = cp->ctl_mask;
					cnt = 0;
					if (buf) {
						cnt = pvr2_i2c_client_describe(
							cp,
							PVR2_I2C_DETAIL_ALL,
							buf,BUFSIZE);
					}
					trace_i2c("Probed: %.*s",cnt,buf);
					if (handler_check(cp)) {
						hdw->i2c_pend_types |=
							PVR2_I2C_PEND_CLIENT;
					}
					cp->pend_mask = msk;
					hdw->i2c_pend_mask |= msk;
					hdw->i2c_pend_types |=
						PVR2_I2C_PEND_REFRESH;
				}
				amask |= cp->ctl_mask;
			}
			hdw->i2c_active_mask = amask;
			if (buf) kfree(buf);
		}
		if (hdw->i2c_pend_types & PVR2_I2C_PEND_STALE) {
			/* Need to do one or more global updates.  Arrange
			   for this to happen. */
			unsigned long m2;
			pvr2_trace(PVR2_TRACE_I2C_CORE,
				   "i2c: PEND_STALE (0x%lx)",
				   hdw->i2c_stale_mask);
			hdw->i2c_pend_types &= ~PVR2_I2C_PEND_STALE;
			list_for_each(item,&hdw->i2c_clients) {
				cp = list_entry(item,struct pvr2_i2c_client,
						list);
				m2 = hdw->i2c_stale_mask;
				m2 &= cp->ctl_mask;
				m2 &= ~cp->pend_mask;
				if (m2) {
					pvr2_trace(PVR2_TRACE_I2C_CORE,
						   "i2c: cp=%p setting 0x%lx",
						   cp,m2);
					cp->pend_mask |= m2;
				}
			}
			hdw->i2c_pend_mask |= hdw->i2c_stale_mask;
			hdw->i2c_stale_mask = 0;
			hdw->i2c_pend_types |= PVR2_I2C_PEND_REFRESH;
		}
		if (hdw->i2c_pend_types & PVR2_I2C_PEND_CLIENT) {
			/* One or more client handlers are asking for an
			   update.  Run through the list of known clients
			   and update each one. */
			pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: PEND_CLIENT");
			hdw->i2c_pend_types &= ~PVR2_I2C_PEND_CLIENT;
			list_for_each_safe(item,nc,&hdw->i2c_clients) {
				cp = list_entry(item,struct pvr2_i2c_client,
						list);
				if (!cp->handler) continue;
				if (!cp->handler->func_table->update) continue;
				pvr2_trace(PVR2_TRACE_I2C_CORE,
					   "i2c: cp=%p update",cp);
				mutex_unlock(&hdw->i2c_list_lock);
				cp->handler->func_table->update(
					cp->handler->func_data);
				mutex_lock(&hdw->i2c_list_lock);
				/* If client's update function set some
				   additional pending bits, account for that
				   here. */
				if (cp->pend_mask & ~hdw->i2c_pend_mask) {
					hdw->i2c_pend_mask |= cp->pend_mask;
					hdw->i2c_pend_types |=
						PVR2_I2C_PEND_REFRESH;
				}
			}
		}
		if (hdw->i2c_pend_types & PVR2_I2C_PEND_REFRESH) {
			const struct pvr2_i2c_op *opf;
			unsigned long pm;
			/* Some actual updates are pending.  Walk through
			   each update type and perform it. */
			pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: PEND_REFRESH"
				   " (0x%lx)",hdw->i2c_pend_mask);
			hdw->i2c_pend_types &= ~PVR2_I2C_PEND_REFRESH;
			pm = hdw->i2c_pend_mask;
			hdw->i2c_pend_mask = 0;
			for (idx = 0, msk = 1; pm; idx++, msk <<= 1) {
				if (!(pm & msk)) continue;
				pm &= ~msk;
				list_for_each(item,&hdw->i2c_clients) {
					cp = list_entry(item,
							struct pvr2_i2c_client,
							list);
					if (cp->pend_mask & msk) {
						cp->pend_mask &= ~msk;
						cp->recv_enable = !0;
					} else {
						cp->recv_enable = 0;
					}
				}
				opf = pvr2_i2c_get_op(idx);
				if (!opf) continue;
				mutex_unlock(&hdw->i2c_list_lock);
				opf->update(hdw);
				mutex_lock(&hdw->i2c_list_lock);
			}
		}
		pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: core_sync END");
	} while (0); mutex_unlock(&hdw->i2c_list_lock);
}

int pvr2_i2c_core_check_stale(struct pvr2_hdw *hdw)
{
	unsigned long msk,sm,pm;
	unsigned int idx;
	const struct pvr2_i2c_op *opf;
	struct list_head *item;
	struct pvr2_i2c_client *cp;
	unsigned int pt = 0;

	pvr2_trace(PVR2_TRACE_I2C_CORE,"pvr2_i2c_core_check_stale BEGIN");

	pm = hdw->i2c_active_mask;
	sm = 0;
	for (idx = 0, msk = 1; pm; idx++, msk <<= 1) {
		if (!(msk & pm)) continue;
		pm &= ~msk;
		opf = pvr2_i2c_get_op(idx);
		if (!opf) continue;
		if (opf->check(hdw)) {
			sm |= msk;
		}
	}
	if (sm) pt |= PVR2_I2C_PEND_STALE;

	list_for_each(item,&hdw->i2c_clients) {
		cp = list_entry(item,struct pvr2_i2c_client,list);
		if (!handler_check(cp)) continue;
		pt |= PVR2_I2C_PEND_CLIENT;
	}

	if (pt) {
		mutex_lock(&hdw->i2c_list_lock); do {
			hdw->i2c_pend_types |= pt;
			hdw->i2c_stale_mask |= sm;
			hdw->i2c_pend_mask |= hdw->i2c_stale_mask;
		} while (0); mutex_unlock(&hdw->i2c_list_lock);
	}

	pvr2_trace(PVR2_TRACE_I2C_CORE,
		   "i2c: types=0x%x stale=0x%lx pend=0x%lx",
		   hdw->i2c_pend_types,
		   hdw->i2c_stale_mask,
		   hdw->i2c_pend_mask);
	pvr2_trace(PVR2_TRACE_I2C_CORE,"pvr2_i2c_core_check_stale END");

	return (hdw->i2c_pend_types & PVR2_I2C_PEND_ALL) != 0;
}

unsigned int pvr2_i2c_client_describe(struct pvr2_i2c_client *cp,
				      unsigned int detail,
				      char *buf,unsigned int maxlen)
{
	unsigned int ccnt,bcnt;
	int spcfl = 0;
	const struct pvr2_i2c_op *opf;

	ccnt = 0;
	if (detail & PVR2_I2C_DETAIL_DEBUG) {
		bcnt = scnprintf(buf,maxlen,
				 "ctxt=%p ctl_mask=0x%lx",
				 cp,cp->ctl_mask);
		ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
		spcfl = !0;
	}
	bcnt = scnprintf(buf,maxlen,
			 "%s%s @ 0x%x",
			 (spcfl ? " " : ""),
			 cp->client->name,
			 cp->client->addr);
	ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
	if ((detail & PVR2_I2C_DETAIL_HANDLER) &&
	    cp->handler && cp->handler->func_table->describe) {
		bcnt = scnprintf(buf,maxlen," (");
		ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
		bcnt = cp->handler->func_table->describe(
			cp->handler->func_data,buf,maxlen);
		ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
		bcnt = scnprintf(buf,maxlen,")");
		ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
	}
	if ((detail & PVR2_I2C_DETAIL_CTLMASK) && cp->ctl_mask) {
		unsigned int idx;
		unsigned long msk,sm;
		int spcfl;
		bcnt = scnprintf(buf,maxlen," [");
		ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
		sm = 0;
		spcfl = 0;
		for (idx = 0, msk = 1; msk; idx++, msk <<= 1) {
			if (!(cp->ctl_mask & msk)) continue;
			opf = pvr2_i2c_get_op(idx);
			if (opf) {
				bcnt = scnprintf(buf,maxlen,"%s%s",
						 spcfl ? " " : "",
						 opf->name);
				ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
				spcfl = !0;
			} else {
				sm |= msk;
			}
		}
		if (sm) {
			bcnt = scnprintf(buf,maxlen,"%s%lx",
					 idx != 0 ? " " : "",sm);
			ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
		}
		bcnt = scnprintf(buf,maxlen,"]");
		ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
	}
	return ccnt;
}

unsigned int pvr2_i2c_report(struct pvr2_hdw *hdw,
			     char *buf,unsigned int maxlen)
{
	unsigned int ccnt,bcnt;
	struct list_head *item;
	struct pvr2_i2c_client *cp;
	ccnt = 0;
	mutex_lock(&hdw->i2c_list_lock); do {
		list_for_each(item,&hdw->i2c_clients) {
			cp = list_entry(item,struct pvr2_i2c_client,list);
			bcnt = pvr2_i2c_client_describe(
				cp,
				(PVR2_I2C_DETAIL_HANDLER|
				 PVR2_I2C_DETAIL_CTLMASK),
				buf,maxlen);
			ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
			bcnt = scnprintf(buf,maxlen,"\n");
			ccnt += bcnt; buf += bcnt; maxlen -= bcnt;
		}
	} while (0); mutex_unlock(&hdw->i2c_list_lock);
	return ccnt;
}

static int pvr2_i2c_attach_inform(struct i2c_client *client)
{
	struct pvr2_hdw *hdw = (struct pvr2_hdw *)(client->adapter->algo_data);
	struct pvr2_i2c_client *cp;
	int fl = !(hdw->i2c_pend_types & PVR2_I2C_PEND_ALL);
	cp = kmalloc(sizeof(*cp),GFP_KERNEL);
	trace_i2c("i2c_attach [client=%s @ 0x%x ctxt=%p]",
		  client->name,
		  client->addr,cp);
	if (!cp) return -ENOMEM;
	memset(cp,0,sizeof(*cp));
	INIT_LIST_HEAD(&cp->list);
	cp->client = client;
	mutex_lock(&hdw->i2c_list_lock); do {
		list_add_tail(&cp->list,&hdw->i2c_clients);
		hdw->i2c_pend_types |= PVR2_I2C_PEND_DETECT;
	} while (0); mutex_unlock(&hdw->i2c_list_lock);
	if (fl) pvr2_hdw_poll_trigger_unlocked(hdw);
	return 0;
}

static int pvr2_i2c_detach_inform(struct i2c_client *client)
{
	struct pvr2_hdw *hdw = (struct pvr2_hdw *)(client->adapter->algo_data);
	struct pvr2_i2c_client *cp;
	struct list_head *item,*nc;
	unsigned long amask = 0;
	int foundfl = 0;
	mutex_lock(&hdw->i2c_list_lock); do {
		list_for_each_safe(item,nc,&hdw->i2c_clients) {
			cp = list_entry(item,struct pvr2_i2c_client,list);
			if (cp->client == client) {
				trace_i2c("pvr2_i2c_detach"
					  " [client=%s @ 0x%x ctxt=%p]",
					  client->name,
					  client->addr,cp);
				if (cp->handler &&
				    cp->handler->func_table->detach) {
					cp->handler->func_table->detach(
						cp->handler->func_data);
				}
				list_del(&cp->list);
				kfree(cp);
				foundfl = !0;
				continue;
			}
			amask |= cp->ctl_mask;
		}
		hdw->i2c_active_mask = amask;
	} while (0); mutex_unlock(&hdw->i2c_list_lock);
	if (!foundfl) {
		trace_i2c("pvr2_i2c_detach [client=%s @ 0x%x ctxt=<unknown>]",
			  client->name,
			  client->addr);
	}
	return 0;
}

static struct i2c_algorithm pvr2_i2c_algo_template = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)
	.id            = I2C_ALGO_BIT | I2C_HW_B_BT848,
#endif
	.master_xfer   = pvr2_i2c_xfer,
	.algo_control  = pvr2_i2c_control,
	.functionality = pvr2_i2c_functionality,
};

static struct i2c_adapter pvr2_i2c_adap_template = {
	.owner         = THIS_MODULE,
	.class	   = I2C_CLASS_TV_ANALOG,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)
	.id            = I2C_ALGO_BIT | I2C_HW_B_BT848,
#else
	.id            = I2C_HW_B_BT848,
#endif
	.client_register = pvr2_i2c_attach_inform,
	.client_unregister = pvr2_i2c_detach_inform,
};

static void do_i2c_scan(struct pvr2_hdw *hdw)
{
	struct i2c_msg msg[1];
	int i,rc;
	msg[0].addr = 0;
	msg[0].flags = I2C_M_RD;
	msg[0].len = 0;
	msg[0].buf = 0;
	printk("%s: i2c scan beginning\n",hdw->name);
	for (i = 0; i < 128; i++) {
		msg[0].addr = i;
		rc = i2c_transfer(&hdw->i2c_adap,msg,
				  sizeof(msg)/sizeof(msg[0]));
		if (rc != 1) continue;
		printk("%s: i2c scan: found device @ 0x%x\n",hdw->name,i);
	}
	printk("%s: i2c scan done.\n",hdw->name);
}

void pvr2_i2c_core_init(struct pvr2_hdw *hdw)
{
	memcpy(&hdw->i2c_adap,&pvr2_i2c_adap_template,sizeof(hdw->i2c_adap));
	memcpy(&hdw->i2c_algo,&pvr2_i2c_algo_template,sizeof(hdw->i2c_algo));
	strlcpy(hdw->i2c_adap.name,hdw->name,sizeof(hdw->i2c_adap.name));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)
	strlcpy(hdw->i2c_algo.name,hdw->name,sizeof(hdw->i2c_algo.name));
#endif
	hdw->i2c_adap.algo = &hdw->i2c_algo;
	hdw->i2c_adap.algo_data = hdw;
	hdw->i2c_pend_mask = 0;
	hdw->i2c_stale_mask = 0;
	hdw->i2c_active_mask = 0;
	INIT_LIST_HEAD(&hdw->i2c_clients);
	mutex_init(&hdw->i2c_list_lock);
	hdw->i2c_linked = !0;
	i2c_add_adapter(&hdw->i2c_adap);
	if (i2c_scan) do_i2c_scan(hdw);
}

void pvr2_i2c_core_done(struct pvr2_hdw *hdw)
{
	if (hdw->i2c_linked) {
		i2c_del_adapter(&hdw->i2c_adap);
		hdw->i2c_linked = 0;
	}
}

/*
  Stuff for Emacs to see, in order to encourage consistent editing style:
  *** Local Variables: ***
  *** mode: c ***
  *** fill-column: 75 ***
  *** tab-width: 8 ***
  *** c-basic-offset: 8 ***
  *** End: ***
  */