| /* |
| * |
| * |
| * 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); |
| } |
| |
| 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); do { |
| 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); |
| 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); |
| } |
| } |
| |
| 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: *** |
| */ |