summaryrefslogtreecommitdiff
path: root/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c')
-rw-r--r--linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c234
1 files changed, 214 insertions, 20 deletions
diff --git a/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c
index 8ec637e5e..4f1f4b000 100644
--- a/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c
+++ b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c
@@ -56,7 +56,7 @@ static int pvr2_i2c_write(struct pvr2_hdw *hdw, /* Context */
"Killing an I2C write to %u that is too large"
" (desired=%u limit=%u)",
i2c_addr,
- length,(sizeof(hdw->cmd_buffer) - 3));
+ length,(unsigned int)(sizeof(hdw->cmd_buffer) - 3));
return -ENOTSUPP;
}
@@ -88,7 +88,7 @@ static int pvr2_i2c_write(struct pvr2_hdw *hdw, /* Context */
}
}
#if 0
- trace_i2c("i2c_write(%d) len=%d ret=%d stat=%d",i2c_addr,length,ret,
+ trace_i2c("i2c_write(0x%x) len=%d ret=%d stat=%d",i2c_addr,length,ret,
hdw->cmd_buffer[0]);
#endif
@@ -112,8 +112,22 @@ static int pvr2_i2c_read(struct pvr2_hdw *hdw, /* Context */
#endif
if (!data) dlen = 0;
- if (dlen > (sizeof(hdw->cmd_buffer) - 4)) return -ENOTSUPP;
- if (res && (rlen > (sizeof(hdw->cmd_buffer) - 1))) return -ENOTSUPP;
+ 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);
@@ -146,7 +160,7 @@ static int pvr2_i2c_read(struct pvr2_hdw *hdw, /* Context */
}
#if 0
- trace_i2c("i2c_read(%d) wlen=%d rlen=%d ret=%d stat=%d",
+ trace_i2c("i2c_read(0x%x) wlen=%d rlen=%d ret=%d stat=%d",
i2c_addr,dlen,rlen,ret,hdw->cmd_buffer[0]);
#endif
/* Copy back the result */
@@ -164,6 +178,130 @@ static int pvr2_i2c_read(struct pvr2_hdw *hdw, /* Context */
return ret;
}
+/* This is the common low level entry point for doing I2C operations to the
+ hardware. */
+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] = 0;
+ 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,
@@ -171,12 +309,24 @@ static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
int num)
{
int ret = -ENOTSUPP;
+ pvr2_i2c_func funcp = 0;
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) {
@@ -184,8 +334,7 @@ static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
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)) {
+ if (funcp(hdw,msgs[0].addr,0,0,0,0)) {
ret = -EIO;
goto done;
}
@@ -202,9 +351,8 @@ static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
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)) {
+ if (funcp(hdw,msgs[0].addr,0,0,
+ msgs[0].buf+offs,bcnt)) {
ret = -EIO;
goto done;
}
@@ -216,13 +364,19 @@ static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
} else {
/* Simple write */
ret = 1;
- if (pvr2_i2c_write(hdw,msgs[0].addr,
- msgs[0].buf,msgs[0].len)) {
+ if (funcp(hdw,msgs[0].addr,
+ msgs[0].buf,msgs[0].len,0,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;
@@ -238,9 +392,9 @@ static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
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)) {
+ if (funcp(hdw,msgs[0].addr,
+ msgs[0].buf,wcnt,
+ msgs[1].buf+offs,bcnt)) {
ret = -EIO;
goto done;
}
@@ -262,18 +416,29 @@ static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
done:
if (pvrusb2_debug & PVR2_TRACE_I2C_TRAF) {
- unsigned int idx;
+ 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,
- msgs[idx].len,
+ 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);
}
@@ -296,7 +461,7 @@ static int pvr2_i2c_control(struct i2c_adapter *adapter,
static u32 pvr2_i2c_functionality(struct i2c_adapter *adap)
{
- return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C;
+ return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA;
}
static int pvr2_i2c_core_singleton(struct i2c_client *cp,
@@ -322,15 +487,26 @@ static int pvr2_i2c_core_singleton(struct i2c_client *cp,
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",cnt,buf);
+ "i2c COMMAND to %.*s (ret=%d)",cnt,buf,stat);
}
- return pvr2_i2c_core_singleton(cp->client,cmd,arg);
+ return stat;
}
int pvr2_i2c_core_cmd(struct pvr2_hdw *hdw,unsigned int cmd,void *arg)
@@ -735,6 +911,24 @@ static void do_i2c_scan(struct pvr2_hdw *hdw)
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));