forked from Minki/linux
a932f50746
This implements a temporary mechanism to "untrack" an i2c module from the old i2c layer. The v4l2-subdev related code in the driver will use this to remove a sub-device from the old i2c layer. In the end, once the old i2c layer is removed, this will also eventually go away. Signed-off-by: Mike Isely <isely@pobox.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
513 lines
14 KiB
C
513 lines
14 KiB
C
/*
|
|
*
|
|
*
|
|
* 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-track.h"
|
|
#include "pvrusb2-hdw-internal.h"
|
|
#include "pvrusb2-debug.h"
|
|
#include "pvrusb2-fx2-cmd.h"
|
|
#include "pvrusb2.h"
|
|
|
|
#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__)
|
|
|
|
|
|
/*
|
|
|
|
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;
|
|
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 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 {
|
|
struct v4l2_tuner *vtp = &hdw->tuner_signal_info;
|
|
memset(vtp,0,sizeof(*vtp));
|
|
list_for_each_entry(cp, &hdw->i2c_clients, list) {
|
|
if (!cp->detected_flag) continue;
|
|
if (!cp->status_poll) continue;
|
|
cp->status_poll(cp);
|
|
}
|
|
hdw->tuner_signal_stale = 0;
|
|
pvr2_trace(PVR2_TRACE_CHIPS,"i2c status poll"
|
|
" type=%u strength=%u audio=0x%x cap=0x%x"
|
|
" low=%u hi=%u",
|
|
vtp->type,
|
|
vtp->signal,vtp->rxsubchans,vtp->capability,
|
|
vtp->rangelow,vtp->rangehigh);
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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 */
|
|
}
|
|
|
|
|
|
/*
|
|
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: ***
|
|
*/
|