This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / drivers / media / video / pvrusb2 / pvrusb2-i2c-core.c
diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c b/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c
new file mode 100644 (file)
index 0000000..7fca479
--- /dev/null
@@ -0,0 +1,941 @@
+/*
+ *
+ *  $Id$
+ *
+ *  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-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 unsigned int pvr2_i2c_client_describe(struct pvr2_i2c_client *cp,
+                                            unsigned int detail,
+                                            char *buf,unsigned int maxlen);
+
+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;
+
+
+       if (!data) length = 0;
+       if (length > (sizeof(hdw->cmd_buffer) - 3)) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Killing an I2C write to %u that is too large"
+                          " (desired=%u limit=%u)",
+                          i2c_addr,
+                          length,(unsigned int)(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]);
+                       }
+               }
+       }
+
+       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;
+
+
+       if (!data) dlen = 0;
+       if (dlen > (sizeof(hdw->cmd_buffer) - 4)) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Killing an I2C read to %u that has wlen too large"
+                          " (desired=%u limit=%u)",
+                          i2c_addr,
+                          dlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 4));
+               return -ENOTSUPP;
+       }
+       if (res && (rlen > (sizeof(hdw->cmd_buffer) - 1))) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Killing an I2C read to %u that has rlen too large"
+                          " (desired=%u limit=%u)",
+                          i2c_addr,
+                          rlen,(unsigned int)(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]);
+                       }
+               }
+       }
+
+       /* 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 the common low level entry point for doing I2C operations to the
+   hardware. */
+static int pvr2_i2c_basic_op(struct pvr2_hdw *hdw,
+                            u8 i2c_addr,
+                            u8 *wdata,
+                            u16 wlen,
+                            u8 *rdata,
+                            u16 rlen)
+{
+       if (!rdata) rlen = 0;
+       if (!wdata) wlen = 0;
+       if (rlen || !wlen) {
+               return pvr2_i2c_read(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+       } else {
+               return pvr2_i2c_write(hdw,i2c_addr,wdata,wlen);
+       }
+}
+
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+
+/* This is a special entry point that is entered if an I2C operation is
+   attempted to a wm8775 chip on model 24xxx hardware.  Autodetect of this
+   part doesn't work, but we know it is really there.  So let's look for
+   the autodetect attempt and just return success if we see that. */
+static int i2c_hack_wm8775(struct pvr2_hdw *hdw,
+                          u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+       if (!(rlen || wlen)) {
+               // This is a probe attempt.  Just let it succeed.
+               return 0;
+       }
+       return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+}
+
+/* This is a special entry point that is entered if an I2C operation is
+   attempted to a cx25840 chip on model 24xxx hardware.  This chip can
+   sometimes wedge itself.  Worse still, when this happens msp3400 can
+   falsely detect this part and then the system gets hosed up after msp3400
+   gets confused and dies.  What we want to do here is try to keep msp3400
+   away and also try to notice if the chip is wedged and send a warning to
+   the system log. */
+static int i2c_hack_cx25840(struct pvr2_hdw *hdw,
+                           u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+       int ret;
+       unsigned int subaddr;
+       u8 wbuf[2];
+       int state = hdw->i2c_cx25840_hack_state;
+
+       if (!(rlen || wlen)) {
+               // Probe attempt - always just succeed and don't bother the
+               // hardware (this helps to make the state machine further
+               // down somewhat easier).
+               return 0;
+       }
+
+       if (state == 3) {
+               return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+       }
+
+       /* We're looking for the exact pattern where the revision register
+          is being read.  The cx25840 module will always look at the
+          revision register first.  Any other pattern of access therefore
+          has to be a probe attempt from somebody else so we'll reject it.
+          Normally we could just let each client just probe the part
+          anyway, but when the cx25840 is wedged, msp3400 will get a false
+          positive and that just screws things up... */
+
+       if (wlen == 0) {
+               switch (state) {
+               case 1: subaddr = 0x0100; break;
+               case 2: subaddr = 0x0101; break;
+               default: goto fail;
+               }
+       } else if (wlen == 2) {
+               subaddr = (wdata[0] << 8) | wdata[1];
+               switch (subaddr) {
+               case 0x0100: state = 1; break;
+               case 0x0101: state = 2; break;
+               default: goto fail;
+               }
+       } else {
+               goto fail;
+       }
+       if (!rlen) goto success;
+       state = 0;
+       if (rlen != 1) goto fail;
+
+       /* If we get to here then we have a legitimate read for one of the
+          two revision bytes, so pass it through. */
+       wbuf[0] = subaddr >> 8;
+       wbuf[1] = subaddr;
+       ret = pvr2_i2c_basic_op(hdw,i2c_addr,wbuf,2,rdata,rlen);
+
+       if ((ret != 0) || (*rdata == 0x04) || (*rdata == 0x0a)) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "WARNING: Detected a wedged cx25840 chip;"
+                          " the device will not work.");
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "WARNING: Try power cycling the pvrusb2 device.");
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "WARNING: Disabling further access to the device"
+                          " to prevent other foul-ups.");
+               // This blocks all further communication with the part.
+               hdw->i2c_func[0x44] = NULL;
+               pvr2_hdw_render_useless(hdw);
+               goto fail;
+       }
+
+       /* Success! */
+       pvr2_trace(PVR2_TRACE_CHIPS,"cx25840 appears to be OK.");
+       state = 3;
+
+ success:
+       hdw->i2c_cx25840_hack_state = state;
+       return 0;
+
+ fail:
+       hdw->i2c_cx25840_hack_state = state;
+       return -EIO;
+}
+
+#endif /* CONFIG_VIDEO_PVRUSB2_24XXX */
+
+/* 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;
+       pvr2_i2c_func funcp = NULL;
+       struct pvr2_hdw *hdw = (struct pvr2_hdw *)(i2c_adap->algo_data);
+
+       if (!num) {
+               ret = -EINVAL;
+               goto done;
+       }
+       if ((msgs[0].flags & I2C_M_NOSTART)) {
+               trace_i2c("i2c refusing I2C_M_NOSTART");
+               goto done;
+       }
+       if (msgs[0].addr < PVR2_I2C_FUNC_CNT) {
+               funcp = hdw->i2c_func[msgs[0].addr];
+       }
+       if (!funcp) {
+               ret = -EIO;
+               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 (funcp(hdw,msgs[0].addr,NULL,0,NULL,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 (funcp(hdw,msgs[0].addr,NULL,0,
+                                         msgs[0].buf+offs,bcnt)) {
+                                       ret = -EIO;
+                                       goto done;
+                               }
+                               offs += bcnt;
+                               tcnt -= bcnt;
+                       }
+                       ret = 1;
+                       goto done;
+               } else {
+                       /* Simple write */
+                       ret = 1;
+                       if (funcp(hdw,msgs[0].addr,
+                                 msgs[0].buf,msgs[0].len,NULL,0)) {
+                               ret = -EIO;
+                       }
+                       goto done;
+               }
+       } else if (num == 2) {
+               if (msgs[0].addr != msgs[1].addr) {
+                       trace_i2c("i2c refusing 2 phase transfer with"
+                                 " conflicting target addresses");
+                       ret = -ENOTSUPP;
+                       goto done;
+               }
+               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 (funcp(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,offs,cnt;
+               for (idx = 0; idx < num; idx++) {
+                       cnt = msgs[idx].len;
+                       printk(KERN_INFO
+                              "pvrusb2 i2c xfer %u/%u:"
+                              " addr=0x%x len=%d %s%s",
+                              idx+1,num,
+                              msgs[idx].addr,
+                              cnt,
+                              (msgs[idx].flags & I2C_M_RD ?
+                               "read" : "write"),
+                              (msgs[idx].flags & I2C_M_NOSTART ?
+                               " nostart" : ""));
+                       if ((ret > 0) || !(msgs[idx].flags & I2C_M_RD)) {
+                               if (cnt > 8) cnt = 8;
+                               printk(" [");
+                               for (offs = 0; offs < (cnt>8?8:cnt); offs++) {
+                                       if (offs) printk(" ");
+                                       printk("%02x",msgs[idx].buf[offs]);
+                               }
+                               if (offs < cnt) printk(" ...");
+                               printk("]");
+                       }
+                       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 | I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA;
+}
+
+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 (!try_module_get(cp->driver->driver.owner)) return -EAGAIN;
+       stat = cp->driver->command(cp,cmd,arg);
+       module_put(cp->driver->driver.owner);
+       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 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;
+}
+
+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;
+               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 = {
+       .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,
+       .id            = I2C_HW_B_BT848,
+       .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 = NULL;
+       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)
+{
+       unsigned int idx;
+
+       // The default action for all possible I2C addresses is just to do
+       // the transfer normally.
+       for (idx = 0; idx < PVR2_I2C_FUNC_CNT; idx++) {
+               hdw->i2c_func[idx] = pvr2_i2c_basic_op;
+       }
+
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+       // If however we're dealing with new hardware, insert some hacks in
+       // the I2C transfer stack to let things work better.
+       if (hdw->hdw_type == PVR2_HDW_TYPE_24XXX) {
+               hdw->i2c_func[0x1b] = i2c_hack_wm8775;
+               hdw->i2c_func[0x44] = i2c_hack_cx25840;
+       }
+#endif
+
+       // Configure the adapter and set up everything else related to it.
+       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));
+       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: ***
+  */