/*
 *
 *  $Id: pvrusb2-i2c-track.c 2226 2009-03-07 05:17:32Z isely $
 *
 *  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 "pvrusb2-options.h"
#include "pvrusb2-i2c-track.h"
#include "pvrusb2-hdw-internal.h"
#include "pvrusb2-debug.h"
#include "pvrusb2-fx2-cmd.h"
#include "pvrusb2.h"
#include "compat.h"

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

#ifdef PVR2_ENABLE_OLD_I2COPS

/*

  This module implements the foundation of a rather large architecture for
  tracking state in all the various V4L I2C modules.  This is obsolete with
  kernels later than roughly 2.6.24, but it is still present in the
  standalone pvrusb2 driver to allow continued operation with older
  kernel.

*/

static unsigned int pvr2_i2c_client_describe(struct pvr2_i2c_client *cp,
					     unsigned int detail,
					     char *buf,unsigned int maxlen);

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;
#ifdef PVR2_ENABLE_OLD_OWNERSTUFF
	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);
#ifdef PVR2_ENABLE_OLD_OWNERSTUFF
	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)
{
	int stat;
	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 (code=%u 0x%x) to %.*s",
			   cmd,cmd,cnt,buf);
	}
	stat = pvr2_i2c_core_singleton(cp->client,cmd,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 (ret=%d)",cnt,buf,stat);
	}
	return stat;
}

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

	if (!hdw) return stat;

	mutex_lock(&hdw->i2c_list_lock);
	list_for_each_entry_safe(cp, ncp, &hdw->i2c_clients, 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_status_poll(struct pvr2_hdw *hdw)
{
	struct pvr2_i2c_client *cp;
	mutex_lock(&hdw->i2c_list_lock); do {
		list_for_each_entry(cp, &hdw->i2c_clients, list) {
			if (!cp->detected_flag) continue;
			if (!cp->status_poll) continue;
			cp->status_poll(cp);
		}
	} while (0); mutex_unlock(&hdw->i2c_list_lock);
}


/* Issue various I2C operations to bring chip-level drivers into sync with
   state stored in this driver. */
void pvr2_i2c_core_sync(struct pvr2_hdw *hdw)
{
	unsigned long msk;
	unsigned int idx;
	struct pvr2_i2c_client *cp, *ncp;

	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_entry(cp, &hdw->i2c_clients, 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_entry(cp, &hdw->i2c_clients, 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_entry_safe(cp, ncp, &hdw->i2c_clients,
						 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_entry(cp, &hdw->i2c_clients,
						    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 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 && opf->check)) continue;
		if (opf->check(hdw)) {
			sm |= msk;
		}
	}
	if (sm) pt |= PVR2_I2C_PEND_STALE;

	list_for_each_entry(cp, &hdw->i2c_clients, list)
		if (handler_check(cp))
			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;
}

static 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;

		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 pvr2_i2c_client *cp;
	ccnt = 0;
	mutex_lock(&hdw->i2c_list_lock); do {
		list_for_each_entry(cp, &hdw->i2c_clients, 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;
}

void pvr2_i2c_track_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 = kzalloc(sizeof(*cp),GFP_KERNEL);
	trace_i2c("i2c_attach [client=%s @ 0x%x ctxt=%p]",
		  client->name,
		  client->addr,cp);
	if (!cp) {
		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
			"Unable to allocate tracking memory for incoming"
			   " i2c module; ignoring module.  This is likely"
			   " going to be a problem.");
		return;
	}
	cp->hdw = hdw;
	INIT_LIST_HEAD(&cp->list);
	cp->client = client;
	mutex_lock(&hdw->i2c_list_lock); do {
		hdw->cropcap_stale = !0;
		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) queue_work(hdw->workqueue,&hdw->worki2csync);
}

static void pvr2_i2c_client_disconnect(struct pvr2_i2c_client *cp)
{
	if (cp->handler && cp->handler->func_table->detach) {
		cp->handler->func_table->detach(cp->handler->func_data);
	}
	list_del(&cp->list);
	kfree(cp);
}

void pvr2_i2c_track_detach_inform(struct i2c_client *client)
{
	struct pvr2_hdw *hdw = (struct pvr2_hdw *)(client->adapter->algo_data);
	struct pvr2_i2c_client *cp, *ncp;
	unsigned long amask = 0;
	int foundfl = 0;
	mutex_lock(&hdw->i2c_list_lock);
	hdw->cropcap_stale = !0;
	list_for_each_entry_safe(cp, ncp, &hdw->i2c_clients, list) {
		if (cp->client == client) {
			trace_i2c("pvr2_i2c_detach"
				  " [client=%s @ 0x%x ctxt=%p]",
				  client->name,
				  client->addr, cp);
			pvr2_i2c_client_disconnect(cp);
			foundfl = !0;
			continue;
		}
		amask |= cp->ctl_mask;
	}
	hdw->i2c_active_mask = amask;
	mutex_unlock(&hdw->i2c_list_lock);
	if (!foundfl) {
		trace_i2c("pvr2_i2c_detach [client=%s @ 0x%x ctxt=<unknown>]",
			  client->name, client->addr);
	}
}

#ifdef PVR2_ENABLE_V4L2SUBDEV
/* This function is used to remove an i2c client from our tracking
   structure if the client happens to be the specified v4l2 sub-device.
   The idea here is to ensure that sub-devices are not also tracked with
   the old tracking mechanism - it's one or the other not both.  This is
   only for debugging.  In a "real" environment, only one of these two
   mechanisms should even be compiled in.  But by enabling both we can
   incrementally test control of each sub-device. */
void pvr2_i2c_untrack_subdev(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
{
	struct i2c_client *client;
	struct pvr2_i2c_client *cp, *ncp;
	unsigned long amask = 0;
	mutex_lock(&hdw->i2c_list_lock);
	list_for_each_entry_safe(cp, ncp, &hdw->i2c_clients, list) {
		client = cp->client;
		if (i2c_get_clientdata(client) == sd) {
			trace_i2c("pvr2_i2c_detach (subdev active)"
				  " [client=%s @ 0x%x ctxt=%p]",
				  client->name, client->addr, cp);
			pvr2_i2c_client_disconnect(cp);
			continue;
		}
		amask |= cp->ctl_mask;
	}
	hdw->i2c_active_mask = amask;
	mutex_unlock(&hdw->i2c_list_lock);
}

#endif
void pvr2_i2c_track_init(struct pvr2_hdw *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);
}

void pvr2_i2c_track_done(struct pvr2_hdw *hdw)
{
	/* Empty for now */
}

#endif

/*
  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: ***
  */
