V4L/DVB (4228a): pvrusb2 to kernel 2.6.18
authorMike Isely <isely@isely.net>
Mon, 26 Jun 2006 23:58:46 +0000 (20:58 -0300)
committerMauro Carvalho Chehab <mchehab@infradead.org>
Tue, 27 Jun 2006 03:17:15 +0000 (00:17 -0300)
Implement V4L2 driver for the Hauppauge PVR USB2 TV tuner.

The Hauppauge PVR USB2 is a USB connected TV tuner with an embedded
cx23416 hardware MPEG2 encoder.  There are two major variants of this
device; this driver handles both.  Any V4L2 application which
understands MPEG2 video stream data should be able to work with this
device.

Signed-off-by: Mike Isely <isely@pobox.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
49 files changed:
Documentation/video4linux/README.pvrusb2 [new file with mode: 0644]
drivers/media/video/Kconfig
drivers/media/video/Makefile
drivers/media/video/pvrusb2/Kconfig [new file with mode: 0644]
drivers/media/video/pvrusb2/Makefile [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-audio.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-audio.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-context.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-context.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-ctrl.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-ctrl.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-debug.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-debugifc.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-debugifc.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-demod.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-demod.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-eeprom.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-eeprom.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-encoder.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-encoder.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-hdw.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-hdw.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-i2c-core.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-i2c-core.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-io.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-io.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-ioread.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-ioread.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-main.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-std.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-std.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-sysfs.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-sysfs.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-tuner.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-tuner.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-util.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-v4l2.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-v4l2.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-video-v4l.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-video-v4l.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-wm8775.c [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2-wm8775.h [new file with mode: 0644]
drivers/media/video/pvrusb2/pvrusb2.h [new file with mode: 0644]

diff --git a/Documentation/video4linux/README.pvrusb2 b/Documentation/video4linux/README.pvrusb2
new file mode 100644 (file)
index 0000000..c73a32c
--- /dev/null
@@ -0,0 +1,212 @@
+
+$Id$
+Mike Isely <isely@pobox.com>
+
+                           pvrusb2 driver
+
+Background:
+
+  This driver is intended for the "Hauppauge WinTV PVR USB 2.0", which
+  is a USB 2.0 hosted TV Tuner.  This driver is a work in progress.
+  Its history started with the reverse-engineering effort by Björn
+  Danielsson <pvrusb2@dax.nu> whose web page can be found here:
+
+    http://pvrusb2.dax.nu/
+
+  From there Aurelien Alleaume <slts@free.fr> began an effort to
+  create a video4linux compatible driver.  I began with Aurelien's
+  last known snapshot and evolved the driver to the state it is in
+  here.
+
+  More information on this driver can be found at:
+
+    http://www.isely.net/pvrusb2.html
+
+
+  This driver has a strong separation of layers.  They are very
+  roughly:
+
+  1a. Low level wire-protocol implementation with the device.
+
+  1b. I2C adaptor implementation and corresponding I2C client drivers
+      implemented elsewhere in V4L.
+
+  1c. High level hardware driver implementation which coordinates all
+      activities that ensure correct operation of the device.
+
+  2.  A "context" layer which manages instancing of driver, setup,
+      tear-down, arbitration, and interaction with high level
+      interfaces appropriately as devices are hotplugged in the
+      system.
+
+  3.  High level interfaces which glue the driver to various published
+      Linux APIs (V4L, sysfs, maybe DVB in the future).
+
+  The most important shearing layer is between the top 2 layers.  A
+  lot of work went into the driver to ensure that any kind of
+  conceivable API can be laid on top of the core driver.  (Yes, the
+  driver internally leverages V4L to do its work but that really has
+  nothing to do with the API published by the driver to the outside
+  world.)  The architecture allows for different APIs to
+  simultaneously access the driver.  I have a strong sense of fairness
+  about APIs and also feel that it is a good design principle to keep
+  implementation and interface isolated from each other.  Thus while
+  right now the V4L high level interface is the most complete, the
+  sysfs high level interface will work equally well for similar
+  functions, and there's no reason I see right now why it shouldn't be
+  possible to produce a DVB high level interface that can sit right
+  alongside V4L.
+
+  NOTE: Complete documentation on the pvrusb2 driver is contained in
+  the html files within the doc directory; these are exactly the same
+  as what is on the web site at the time.  Browse those files
+  (especially the FAQ) before asking questions.
+
+
+Building
+
+  To build these modules essentially amounts to just running "Make",
+  but you need the kernel source tree nearby and you will likely also
+  want to set a few controlling environment variables first in order
+  to link things up with that source tree.  Please see the Makefile
+  here for comments that explain how to do that.
+
+
+Source file list / functional overview:
+
+  (Note: The term "module" used below generally refers to loosely
+  defined functional units within the pvrusb2 driver and bears no
+  relation to the Linux kernel's concept of a loadable module.)
+
+  pvrusb2-audio.[ch] - This is glue logic that resides between this
+    driver and the msp3400.ko I2C client driver (which is found
+    elsewhere in V4L).
+
+  pvrusb2-context.[ch] - This module implements the context for an
+    instance of the driver.  Everything else eventually ties back to
+    or is otherwise instanced within the data structures implemented
+    here.  Hotplugging is ultimately coordinated here.  All high level
+    interfaces tie into the driver through this module.  This module
+    helps arbitrate each interface's access to the actual driver core,
+    and is designed to allow concurrent access through multiple
+    instances of multiple interfaces (thus you can for example change
+    the tuner's frequency through sysfs while simultaneously streaming
+    video through V4L out to an instance of mplayer).
+
+  pvrusb2-debug.h - This header defines a printk() wrapper and a mask
+    of debugging bit definitions for the various kinds of debug
+    messages that can be enabled within the driver.
+
+  pvrusb2-debugifc.[ch] - This module implements a crude command line
+    oriented debug interface into the driver.  Aside from being part
+    of the process for implementing manual firmware extraction (see
+    the pvrusb2 web site mentioned earlier), probably I'm the only one
+    who has ever used this.  It is mainly a debugging aid.
+
+  pvrusb2-eeprom.[ch] - This is glue logic that resides between this
+    driver the tveeprom.ko module, which is itself implemented
+    elsewhere in V4L.
+
+  pvrusb2-encoder.[ch] - This module implements all protocol needed to
+    interact with the Conexant mpeg2 encoder chip within the pvrusb2
+    device.  It is a crude echo of corresponding logic in ivtv,
+    however the design goals (strict isolation) and physical layer
+    (proxy through USB instead of PCI) are enough different that this
+    implementation had to be completely different.
+
+  pvrusb2-hdw-internal.h - This header defines the core data structure
+    in the driver used to track ALL internal state related to control
+    of the hardware.  Nobody outside of the core hardware-handling
+    modules should have any business using this header.  All external
+    access to the driver should be through one of the high level
+    interfaces (e.g. V4L, sysfs, etc), and in fact even those high
+    level interfaces are restricted to the API defined in
+    pvrusb2-hdw.h and NOT this header.
+
+  pvrusb2-hdw.h - This header defines the full internal API for
+    controlling the hardware.  High level interfaces (e.g. V4L, sysfs)
+    will work through here.
+
+  pvrusb2-hdw.c - This module implements all the various bits of logic
+    that handle overall control of a specific pvrusb2 device.
+    (Policy, instantiation, and arbitration of pvrusb2 devices fall
+    within the jurisdiction of pvrusb-context not here).
+
+  pvrusb2-i2c-chips-*.c - These modules implement the glue logic to
+    tie together and configure various I2C modules as they attach to
+    the I2C bus.  There are two versions of this file.  The "v4l2"
+    version is intended to be used in-tree alongside V4L, where we
+    implement just the logic that makes sense for a pure V4L
+    environment.  The "all" version is intended for use outside of
+    V4L, where we might encounter other possibly "challenging" modules
+    from ivtv or older kernel snapshots (or even the support modules
+    in the standalone snapshot).
+
+  pvrusb2-i2c-cmd-v4l1.[ch] - This module implements generic V4L1
+    compatible commands to the I2C modules.  It is here where state
+    changes inside the pvrusb2 driver are translated into V4L1
+    commands that are in turn send to the various I2C modules.
+
+  pvrusb2-i2c-cmd-v4l2.[ch] - This module implements generic V4L2
+    compatible commands to the I2C modules.  It is here where state
+    changes inside the pvrusb2 driver are translated into V4L2
+    commands that are in turn send to the various I2C modules.
+
+  pvrusb2-i2c-core.[ch] - This module provides an implementation of a
+    kernel-friendly I2C adaptor driver, through which other external
+    I2C client drivers (e.g. msp3400, tuner, lirc) may connect and
+    operate corresponding chips within the the pvrusb2 device.  It is
+    through here that other V4L modules can reach into this driver to
+    operate specific pieces (and those modules are in turn driven by
+    glue logic which is coordinated by pvrusb2-hdw, doled out by
+    pvrusb2-context, and then ultimately made available to users
+    through one of the high level interfaces).
+
+  pvrusb2-io.[ch] - This module implements a very low level ring of
+    transfer buffers, required in order to stream data from the
+    device.  This module is *very* low level.  It only operates the
+    buffers and makes no attempt to define any policy or mechanism for
+    how such buffers might be used.
+
+  pvrusb2-ioread.[ch] - This module layers on top of pvrusb2-io.[ch]
+    to provide a streaming API usable by a read() system call style of
+    I/O.  Right now this is the only layer on top of pvrusb2-io.[ch],
+    however the underlying architecture here was intended to allow for
+    other styles of I/O to be implemented with additonal modules, like
+    mmap()'ed buffers or something even more exotic.
+
+  pvrusb2-main.c - This is the top level of the driver.  Module level
+    and USB core entry points are here.  This is our "main".
+
+  pvrusb2-sysfs.[ch] - This is the high level interface which ties the
+    pvrusb2 driver into sysfs.  Through this interface you can do
+    everything with the driver except actually stream data.
+
+  pvrusb2-tuner.[ch] - This is glue logic that resides between this
+    driver and the tuner.ko I2C client driver (which is found
+    elsewhere in V4L).
+
+  pvrusb2-util.h - This header defines some common macros used
+    throughout the driver.  These macros are not really specific to
+    the driver, but they had to go somewhere.
+
+  pvrusb2-v4l2.[ch] - This is the high level interface which ties the
+    pvrusb2 driver into video4linux.  It is through here that V4L
+    applications can open and operate the driver in the usual V4L
+    ways.  Note that **ALL** V4L functionality is published only
+    through here and nowhere else.
+
+  pvrusb2-video-*.[ch] - This is glue logic that resides between this
+    driver and the saa711x.ko I2C client driver (which is found
+    elsewhere in V4L).  Note that saa711x.ko used to be known as
+    saa7115.ko in ivtv.  There are two versions of this; one is
+    selected depending on the particular saa711[5x].ko that is found.
+
+  pvrusb2.h - This header contains compile time tunable parameters
+    (and at the moment the driver has very little that needs to be
+    tuned).
+
+
+  -Mike Isely
+  isely@pobox.com
+
index e429049..6d532f1 100644 (file)
@@ -445,6 +445,8 @@ endmenu # encoder / decoder chips
 menu "V4L USB devices"
        depends on USB && VIDEO_DEV
 
+source "drivers/media/video/pvrusb2/Kconfig"
+
 source "drivers/media/video/em28xx/Kconfig"
 
 config USB_DSBR
index 6c401b4..353d61c 100644 (file)
@@ -47,6 +47,7 @@ obj-$(CONFIG_VIDEO_SAA7134) += ir-kbd-i2c.o saa7134/
 obj-$(CONFIG_VIDEO_CX88) += cx88/
 obj-$(CONFIG_VIDEO_EM28XX) += em28xx/
 obj-$(CONFIG_VIDEO_EM28XX) += tvp5150.o
+obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/
 obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
 obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o
 obj-$(CONFIG_VIDEO_TLV320AIC23B) += tlv320aic23b.o
diff --git a/drivers/media/video/pvrusb2/Kconfig b/drivers/media/video/pvrusb2/Kconfig
new file mode 100644 (file)
index 0000000..7e727fe
--- /dev/null
@@ -0,0 +1,62 @@
+config VIDEO_PVRUSB2
+       tristate "Hauppauge WinTV-PVR USB2 support"
+       depends on VIDEO_V4L2 && USB && I2C && EXPERIMENTAL
+       select FW_LOADER
+       select VIDEO_TUNER
+       select VIDEO_TVEEPROM
+       select VIDEO_CX2341X
+       select VIDEO_SAA711X
+       select VIDEO_MSP3400
+       ---help---
+         This is a video4linux driver for Conexant 23416 based
+         usb2 personal video recorder devices.
+
+         To compile this driver as a module, choose M here: the
+         module will be called pvrusb2
+
+config VIDEO_PVRUSB2_24XXX
+       bool "Hauppauge WinTV-PVR USB2 support for 24xxx model series"
+       depends on VIDEO_PVRUSB2 && EXPERIMENTAL
+       select VIDEO_CX25840
+       select VIDEO_WM8775
+       ---help---
+         This option enables inclusion of additional logic to operate
+         newer WinTV-PVR USB2 devices whose model number is of the
+         form "24xxx" (leading prefix of "24" followed by 3 digits).
+         To see if you may need this option, examine the white
+         sticker on the underside of your device.  Enabling this
+         option will not harm support for older devices, however it
+         is a separate option because of the experimental nature of
+         this new feature.
+
+         If you are in doubt, say N.
+
+         Note: This feature is _very_ experimental.  You have been
+         warned.
+
+config VIDEO_PVRUSB2_SYSFS
+       bool "pvrusb2 sysfs support (EXPERIMENTAL)"
+       default y
+       depends on VIDEO_PVRUSB2 && SYSFS && EXPERIMENTAL
+       ---help---
+         This option enables the operation of a sysfs based
+         interface for query and control of the pvrusb2 driver.
+
+         This is not generally needed for v4l applications,
+         although certain applications are optimized to take
+         advantage of this feature.
+
+         If you are in doubt, say Y.
+
+         Note: This feature is experimental and subject to change.
+
+config VIDEO_PVRUSB2_DEBUGIFC
+       bool "pvrusb2 debug interface"
+       depends on VIDEO_PVRUSB2_SYSFS
+       ---help---
+         This option enables the inclusion of a debug interface
+         in the pvrusb2 driver, hosted through sysfs.
+
+         You do not need to select this option unless you plan
+         on debugging the driver or performing a manual firmware
+         extraction.
diff --git a/drivers/media/video/pvrusb2/Makefile b/drivers/media/video/pvrusb2/Makefile
new file mode 100644 (file)
index 0000000..fed603a
--- /dev/null
@@ -0,0 +1,18 @@
+obj-pvrusb2-sysfs-$(CONFIG_VIDEO_PVRUSB2_SYSFS) := pvrusb2-sysfs.o
+obj-pvrusb2-debugifc-$(CONFIG_VIDEO_PVRUSB2_DEBUGIFC) := pvrusb2-debugifc.o
+
+obj-pvrusb2-24xxx-$(CONFIG_VIDEO_PVRUSB2_24XXX) := \
+                  pvrusb2-cx2584x-v4l.o \
+                  pvrusb2-wm8775.o
+
+pvrusb2-objs   := pvrusb2-i2c-core.o pvrusb2-i2c-cmd-v4l2.o \
+                  pvrusb2-audio.o pvrusb2-i2c-chips-v4l2.o \
+                  pvrusb2-encoder.o pvrusb2-video-v4l.o \
+                  pvrusb2-eeprom.o pvrusb2-tuner.o pvrusb2-demod.o \
+                  pvrusb2-main.o pvrusb2-hdw.o pvrusb2-v4l2.o \
+                  pvrusb2-ctrl.o pvrusb2-std.o \
+                  pvrusb2-context.o pvrusb2-io.o pvrusb2-ioread.o \
+                  $(obj-pvrusb2-24xxx-y) \
+                  $(obj-pvrusb2-sysfs-y) $(obj-pvrusb2-debugifc-y)
+
+obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2.o
diff --git a/drivers/media/video/pvrusb2/pvrusb2-audio.c b/drivers/media/video/pvrusb2/pvrusb2-audio.c
new file mode 100644 (file)
index 0000000..313d2dc
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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-audio.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/msp3400.h>
+#include <media/v4l2-common.h>
+
+struct pvr2_msp3400_handler {
+       struct pvr2_hdw *hdw;
+       struct pvr2_i2c_client *client;
+       struct pvr2_i2c_handler i2c_handler;
+       struct pvr2_audio_stat astat;
+       unsigned long stale_mask;
+};
+
+
+/* This function selects the correct audio input source */
+static void set_stereo(struct pvr2_msp3400_handler *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       struct v4l2_routing route;
+
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c msp3400 v4l2 set_stereo");
+
+       if (hdw->input_val == PVR2_CVAL_INPUT_TV) {
+               struct v4l2_tuner vt;
+               memset(&vt,0,sizeof(vt));
+               vt.audmode = hdw->audiomode_val;
+               pvr2_i2c_client_cmd(ctxt->client,VIDIOC_S_TUNER,&vt);
+       }
+
+       route.input = MSP_INPUT_DEFAULT;
+       route.output = MSP_OUTPUT(MSP_SC_IN_DSP_SCART1);
+       switch (hdw->input_val) {
+       case PVR2_CVAL_INPUT_TV:
+               break;
+       case PVR2_CVAL_INPUT_RADIO:
+               /* Assume that msp34xx also handle FM decoding, in which case
+                  we're still using the tuner. */
+               /* HV: actually it is more likely to be the SCART2 input if
+                  the ivtv experience is any indication. */
+               route.input = MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1,
+                                   MSP_DSP_IN_SCART, MSP_DSP_IN_SCART);
+               break;
+       case PVR2_CVAL_INPUT_SVIDEO:
+       case PVR2_CVAL_INPUT_COMPOSITE:
+               /* SCART 1 input */
+               route.input = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1,
+                                   MSP_DSP_IN_SCART, MSP_DSP_IN_SCART);
+               break;
+       }
+       pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_AUDIO_ROUTING,&route);
+}
+
+
+static int check_stereo(struct pvr2_msp3400_handler *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       return (hdw->input_dirty ||
+               hdw->audiomode_dirty);
+}
+
+
+struct pvr2_msp3400_ops {
+       void (*update)(struct pvr2_msp3400_handler *);
+       int (*check)(struct pvr2_msp3400_handler *);
+};
+
+
+static const struct pvr2_msp3400_ops msp3400_ops[] = {
+       { .update = set_stereo, .check = check_stereo},
+};
+
+
+static int msp3400_check(struct pvr2_msp3400_handler *ctxt)
+{
+       unsigned long msk;
+       unsigned int idx;
+
+       for (idx = 0; idx < sizeof(msp3400_ops)/sizeof(msp3400_ops[0]);
+            idx++) {
+               msk = 1 << idx;
+               if (ctxt->stale_mask & msk) continue;
+               if (msp3400_ops[idx].check(ctxt)) {
+                       ctxt->stale_mask |= msk;
+               }
+       }
+       return ctxt->stale_mask != 0;
+}
+
+
+static void msp3400_update(struct pvr2_msp3400_handler *ctxt)
+{
+       unsigned long msk;
+       unsigned int idx;
+
+       for (idx = 0; idx < sizeof(msp3400_ops)/sizeof(msp3400_ops[0]);
+            idx++) {
+               msk = 1 << idx;
+               if (!(ctxt->stale_mask & msk)) continue;
+               ctxt->stale_mask &= ~msk;
+               msp3400_ops[idx].update(ctxt);
+       }
+}
+
+
+/* This reads back the current signal type */
+static int get_audio_status(struct pvr2_msp3400_handler *ctxt)
+{
+       struct v4l2_tuner vt;
+       int stat;
+
+       memset(&vt,0,sizeof(vt));
+       stat = pvr2_i2c_client_cmd(ctxt->client,VIDIOC_G_TUNER,&vt);
+       if (stat < 0) return stat;
+
+       ctxt->hdw->flag_stereo = (vt.audmode & V4L2_TUNER_MODE_STEREO) != 0;
+       ctxt->hdw->flag_bilingual =
+               (vt.audmode & V4L2_TUNER_MODE_LANG2) != 0;
+       return 0;
+}
+
+
+static void pvr2_msp3400_detach(struct pvr2_msp3400_handler *ctxt)
+{
+       ctxt->client->handler = 0;
+       ctxt->hdw->audio_stat = 0;
+       kfree(ctxt);
+}
+
+
+static unsigned int pvr2_msp3400_describe(struct pvr2_msp3400_handler *ctxt,
+                                         char *buf,unsigned int cnt)
+{
+       return scnprintf(buf,cnt,"handler: pvrusb2-audio v4l2");
+}
+
+
+const static struct pvr2_i2c_handler_functions msp3400_funcs = {
+       .detach = (void (*)(void *))pvr2_msp3400_detach,
+       .check = (int (*)(void *))msp3400_check,
+       .update = (void (*)(void *))msp3400_update,
+       .describe = (unsigned int (*)(void *,char *,unsigned int))pvr2_msp3400_describe,
+};
+
+
+int pvr2_i2c_msp3400_setup(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp)
+{
+       struct pvr2_msp3400_handler *ctxt;
+       if (hdw->audio_stat) return 0;
+       if (cp->handler) return 0;
+
+       ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL);
+       if (!ctxt) return 0;
+       memset(ctxt,0,sizeof(*ctxt));
+
+       ctxt->i2c_handler.func_data = ctxt;
+       ctxt->i2c_handler.func_table = &msp3400_funcs;
+       ctxt->client = cp;
+       ctxt->hdw = hdw;
+       ctxt->astat.ctxt = ctxt;
+       ctxt->astat.status = (int (*)(void *))get_audio_status;
+       ctxt->astat.detach = (void (*)(void *))pvr2_msp3400_detach;
+       ctxt->stale_mask = (1 << (sizeof(msp3400_ops)/
+                                 sizeof(msp3400_ops[0]))) - 1;
+       cp->handler = &ctxt->i2c_handler;
+       hdw->audio_stat = &ctxt->astat;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x msp3400 V4L2 handler set up",
+                  cp->client->addr);
+       return !0;
+}
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-audio.h b/drivers/media/video/pvrusb2/pvrusb2-audio.h
new file mode 100644 (file)
index 0000000..536339b
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+#ifndef __PVRUSB2_AUDIO_H
+#define __PVRUSB2_AUDIO_H
+
+#include "pvrusb2-i2c-core.h"
+
+int pvr2_i2c_msp3400_setup(struct pvr2_hdw *,struct pvr2_i2c_client *);
+
+#endif /* __PVRUSB2_AUDIO_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-context.c b/drivers/media/video/pvrusb2/pvrusb2-context.c
new file mode 100644 (file)
index 0000000..40dc598
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ *  $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-context.h"
+#include "pvrusb2-io.h"
+#include "pvrusb2-ioread.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <asm/semaphore.h>
+
+
+static void pvr2_context_destroy(struct pvr2_context *mp)
+{
+       if (mp->hdw) pvr2_hdw_destroy(mp->hdw);
+       pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr_main id=%p",mp);
+       flush_workqueue(mp->workqueue);
+       destroy_workqueue(mp->workqueue);
+       kfree(mp);
+}
+
+
+static void pvr2_context_trigger_poll(struct pvr2_context *mp)
+{
+       queue_work(mp->workqueue,&mp->workpoll);
+}
+
+
+static void pvr2_context_poll(struct pvr2_context *mp)
+{
+       pvr2_context_enter(mp); do {
+               pvr2_hdw_poll(mp->hdw);
+       } while (0); pvr2_context_exit(mp);
+}
+
+
+static void pvr2_context_setup(struct pvr2_context *mp)
+{
+       pvr2_context_enter(mp); do {
+               if (!pvr2_hdw_dev_ok(mp->hdw)) break;
+               pvr2_hdw_setup(mp->hdw);
+               pvr2_hdw_setup_poll_trigger(
+                       mp->hdw,
+                       (void (*)(void *))pvr2_context_trigger_poll,
+                       mp);
+               if (!pvr2_hdw_dev_ok(mp->hdw)) break;
+               if (!pvr2_hdw_init_ok(mp->hdw)) break;
+               mp->video_stream.stream = pvr2_hdw_get_video_stream(mp->hdw);
+               if (mp->setup_func) {
+                       mp->setup_func(mp);
+               }
+       } while (0); pvr2_context_exit(mp);
+}
+
+
+struct pvr2_context *pvr2_context_create(
+       struct usb_interface *intf,
+       const struct usb_device_id *devid,
+       void (*setup_func)(struct pvr2_context *))
+{
+       struct pvr2_context *mp = 0;
+       mp = kmalloc(sizeof(*mp),GFP_KERNEL);
+       if (!mp) goto done;
+       memset(mp,0,sizeof(*mp));
+       pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_main id=%p",mp);
+       mp->setup_func = setup_func;
+       mutex_init(&mp->mutex);
+       mp->hdw = pvr2_hdw_create(intf,devid);
+       if (!mp->hdw) {
+               pvr2_context_destroy(mp);
+               mp = 0;
+               goto done;
+       }
+
+       mp->workqueue = create_singlethread_workqueue("pvrusb2");
+       INIT_WORK(&mp->workinit,(void (*)(void*))pvr2_context_setup,mp);
+       INIT_WORK(&mp->workpoll,(void (*)(void*))pvr2_context_poll,mp);
+       queue_work(mp->workqueue,&mp->workinit);
+ done:
+       return mp;
+}
+
+
+void pvr2_context_enter(struct pvr2_context *mp)
+{
+       mutex_lock(&mp->mutex);
+       pvr2_trace(PVR2_TRACE_CREG,"pvr2_context_enter(id=%p)",mp);
+}
+
+
+void pvr2_context_exit(struct pvr2_context *mp)
+{
+       int destroy_flag = 0;
+       if (!(mp->mc_first || !mp->disconnect_flag)) {
+               destroy_flag = !0;
+       }
+       pvr2_trace(PVR2_TRACE_CREG,"pvr2_context_exit(id=%p) outside",mp);
+       mutex_unlock(&mp->mutex);
+       if (destroy_flag) pvr2_context_destroy(mp);
+}
+
+
+static void pvr2_context_run_checks(struct pvr2_context *mp)
+{
+       struct pvr2_channel *ch1,*ch2;
+       for (ch1 = mp->mc_first; ch1; ch1 = ch2) {
+               ch2 = ch1->mc_next;
+               if (ch1->check_func) {
+                       ch1->check_func(ch1);
+               }
+       }
+}
+
+
+void pvr2_context_disconnect(struct pvr2_context *mp)
+{
+       pvr2_context_enter(mp); do {
+               pvr2_hdw_disconnect(mp->hdw);
+               mp->disconnect_flag = !0;
+               pvr2_context_run_checks(mp);
+       } while (0); pvr2_context_exit(mp);
+}
+
+
+void pvr2_channel_init(struct pvr2_channel *cp,struct pvr2_context *mp)
+{
+       cp->hdw = mp->hdw;
+       cp->mc_head = mp;
+       cp->mc_next = 0;
+       cp->mc_prev = mp->mc_last;
+       if (mp->mc_last) {
+               mp->mc_last->mc_next = cp;
+       } else {
+               mp->mc_first = cp;
+       }
+       mp->mc_last = cp;
+}
+
+
+static void pvr2_channel_disclaim_stream(struct pvr2_channel *cp)
+{
+       if (!cp->stream) return;
+       pvr2_stream_kill(cp->stream->stream);
+       cp->stream->user = 0;
+       cp->stream = 0;
+}
+
+
+void pvr2_channel_done(struct pvr2_channel *cp)
+{
+       struct pvr2_context *mp = cp->mc_head;
+       pvr2_channel_disclaim_stream(cp);
+       if (cp->mc_next) {
+               cp->mc_next->mc_prev = cp->mc_prev;
+       } else {
+               mp->mc_last = cp->mc_prev;
+       }
+       if (cp->mc_prev) {
+               cp->mc_prev->mc_next = cp->mc_next;
+       } else {
+               mp->mc_first = cp->mc_next;
+       }
+       cp->hdw = 0;
+}
+
+
+int pvr2_channel_claim_stream(struct pvr2_channel *cp,
+                             struct pvr2_context_stream *sp)
+{
+       int code = 0;
+       pvr2_context_enter(cp->mc_head); do {
+               if (sp == cp->stream) break;
+               if (sp->user) {
+                       code = -EBUSY;
+                       break;
+               }
+               pvr2_channel_disclaim_stream(cp);
+               if (!sp) break;
+               sp->user = cp;
+               cp->stream = sp;
+       } while (0); pvr2_context_exit(cp->mc_head);
+       return code;
+}
+
+
+// This is the marker for the real beginning of a legitimate mpeg2 stream.
+static char stream_sync_key[] = {
+       0x00, 0x00, 0x01, 0xba,
+};
+
+struct pvr2_ioread *pvr2_channel_create_mpeg_stream(
+       struct pvr2_context_stream *sp)
+{
+       struct pvr2_ioread *cp;
+       cp = pvr2_ioread_create();
+       if (!cp) return 0;
+       pvr2_ioread_setup(cp,sp->stream);
+       pvr2_ioread_set_sync_key(cp,stream_sync_key,sizeof(stream_sync_key));
+       return cp;
+}
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-context.h b/drivers/media/video/pvrusb2/pvrusb2-context.h
new file mode 100644 (file)
index 0000000..6327fa1
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_BASE_H
+#define __PVRUSB2_BASE_H
+
+#include <linux/mutex.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+struct pvr2_hdw;     /* hardware interface - defined elsewhere */
+struct pvr2_stream;  /* stream interface - defined elsewhere */
+
+struct pvr2_context;        /* All central state */
+struct pvr2_channel;        /* One I/O pathway to a user */
+struct pvr2_context_stream; /* Wrapper for a stream */
+struct pvr2_crit_reg;       /* Critical region pointer */
+struct pvr2_ioread;         /* Low level stream structure */
+
+struct pvr2_context_stream {
+       struct pvr2_channel *user;
+       struct pvr2_stream *stream;
+};
+
+struct pvr2_context {
+       struct pvr2_channel *mc_first;
+       struct pvr2_channel *mc_last;
+       struct pvr2_hdw *hdw;
+       struct pvr2_context_stream video_stream;
+       struct mutex mutex;
+       int disconnect_flag;
+
+       /* Called after pvr2_context initialization is complete */
+       void (*setup_func)(struct pvr2_context *);
+
+       /* Work queue overhead for out-of-line processing */
+       struct workqueue_struct *workqueue;
+       struct work_struct workinit;
+       struct work_struct workpoll;
+};
+
+struct pvr2_channel {
+       struct pvr2_context *mc_head;
+       struct pvr2_channel *mc_next;
+       struct pvr2_channel *mc_prev;
+       struct pvr2_context_stream *stream;
+       struct pvr2_hdw *hdw;
+       void (*check_func)(struct pvr2_channel *);
+};
+
+void pvr2_context_enter(struct pvr2_context *);
+void pvr2_context_exit(struct pvr2_context *);
+
+struct pvr2_context *pvr2_context_create(struct usb_interface *intf,
+                                        const struct usb_device_id *devid,
+                                        void (*setup_func)(struct pvr2_context *));
+void pvr2_context_disconnect(struct pvr2_context *);
+
+void pvr2_channel_init(struct pvr2_channel *,struct pvr2_context *);
+void pvr2_channel_done(struct pvr2_channel *);
+int pvr2_channel_claim_stream(struct pvr2_channel *,
+                             struct pvr2_context_stream *);
+struct pvr2_ioread *pvr2_channel_create_mpeg_stream(
+       struct pvr2_context_stream *);
+
+
+#endif /* __PVRUSB2_CONTEXT_H */
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-ctrl.c b/drivers/media/video/pvrusb2/pvrusb2-ctrl.c
new file mode 100644 (file)
index 0000000..3577d5b
--- /dev/null
@@ -0,0 +1,544 @@
+/*
+ *
+ *  $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-ctrl.h"
+#include "pvrusb2-hdw-internal.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mutex.h>
+
+
+/* Set the given control. */
+int pvr2_ctrl_set_value(struct pvr2_ctrl *cptr,int val)
+{
+       return pvr2_ctrl_set_mask_value(cptr,~0,val);
+}
+
+
+/* Set/clear specific bits of the given control. */
+int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *cptr,int mask,int val)
+{
+       int ret = 0;
+       if (!cptr) return -EINVAL;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               if (cptr->info->set_value != 0) {
+                       if (cptr->info->type == pvr2_ctl_bitmask) {
+                               mask &= cptr->info->def.type_bitmask.valid_bits;
+                       } else if (cptr->info->type == pvr2_ctl_int) {
+                               if (val < cptr->info->def.type_int.min_value) {
+                                       break;
+                               }
+                               if (val > cptr->info->def.type_int.max_value) {
+                                       break;
+                               }
+                       } else if (cptr->info->type == pvr2_ctl_enum) {
+                               if (val >= cptr->info->def.type_enum.count) {
+                                       break;
+                               }
+                       }
+                       ret = cptr->info->set_value(cptr,mask,val);
+               } else {
+                       ret = -EPERM;
+               }
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Get the current value of the given control. */
+int pvr2_ctrl_get_value(struct pvr2_ctrl *cptr,int *valptr)
+{
+       int ret = 0;
+       if (!cptr) return -EINVAL;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               ret = cptr->info->get_value(cptr,valptr);
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Retrieve control's type */
+enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *cptr)
+{
+       if (!cptr) return pvr2_ctl_int;
+       return cptr->info->type;
+}
+
+
+/* Retrieve control's maximum value (int type) */
+int pvr2_ctrl_get_max(struct pvr2_ctrl *cptr)
+{
+       int ret = 0;
+       if (!cptr) return 0;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               if (cptr->info->type == pvr2_ctl_int) {
+                       ret = cptr->info->def.type_int.max_value;
+               }
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Retrieve control's minimum value (int type) */
+int pvr2_ctrl_get_min(struct pvr2_ctrl *cptr)
+{
+       int ret = 0;
+       if (!cptr) return 0;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               if (cptr->info->type == pvr2_ctl_int) {
+                       ret = cptr->info->def.type_int.min_value;
+               }
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Retrieve control's default value (any type) */
+int pvr2_ctrl_get_def(struct pvr2_ctrl *cptr)
+{
+       int ret = 0;
+       if (!cptr) return 0;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               if (cptr->info->type == pvr2_ctl_int) {
+                       ret = cptr->info->default_value;
+               }
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Retrieve control's enumeration count (enum only) */
+int pvr2_ctrl_get_cnt(struct pvr2_ctrl *cptr)
+{
+       int ret = 0;
+       if (!cptr) return 0;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               if (cptr->info->type == pvr2_ctl_enum) {
+                       ret = cptr->info->def.type_enum.count;
+               }
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Retrieve control's valid mask bits (bit mask only) */
+int pvr2_ctrl_get_mask(struct pvr2_ctrl *cptr)
+{
+       int ret = 0;
+       if (!cptr) return 0;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               if (cptr->info->type == pvr2_ctl_bitmask) {
+                       ret = cptr->info->def.type_bitmask.valid_bits;
+               }
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Retrieve the control's name */
+const char *pvr2_ctrl_get_name(struct pvr2_ctrl *cptr)
+{
+       if (!cptr) return 0;
+       return cptr->info->name;
+}
+
+
+/* Retrieve the control's desc */
+const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *cptr)
+{
+       if (!cptr) return 0;
+       return cptr->info->desc;
+}
+
+
+/* Retrieve a control enumeration or bit mask value */
+int pvr2_ctrl_get_valname(struct pvr2_ctrl *cptr,int val,
+                         char *bptr,unsigned int bmax,
+                         unsigned int *blen)
+{
+       int ret = -EINVAL;
+       if (!cptr) return 0;
+       *blen = 0;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               if (cptr->info->type == pvr2_ctl_enum) {
+                       const char **names;
+                       names = cptr->info->def.type_enum.value_names;
+                       if ((val >= 0) &&
+                           (val < cptr->info->def.type_enum.count)) {
+                               if (names[val]) {
+                                       *blen = scnprintf(
+                                               bptr,bmax,"%s",
+                                               names[val]);
+                               } else {
+                                       *blen = 0;
+                               }
+                               ret = 0;
+                       }
+               } else if (cptr->info->type == pvr2_ctl_bitmask) {
+                       const char **names;
+                       unsigned int idx;
+                       int msk;
+                       names = cptr->info->def.type_bitmask.bit_names;
+                       val &= cptr->info->def.type_bitmask.valid_bits;
+                       for (idx = 0, msk = 1; val; idx++, msk <<= 1) {
+                               if (val & msk) {
+                                       *blen = scnprintf(bptr,bmax,"%s",
+                                                         names[idx]);
+                                       ret = 0;
+                                       break;
+                               }
+                       }
+               }
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Return true if control is writable */
+int pvr2_ctrl_is_writable(struct pvr2_ctrl *cptr)
+{
+       if (!cptr) return 0;
+       return cptr->info->set_value != 0;
+}
+
+
+/* Return true if control has custom symbolic representation */
+int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *cptr)
+{
+       if (!cptr) return 0;
+       if (!cptr->info->val_to_sym) return 0;
+       if (!cptr->info->sym_to_val) return 0;
+       return !0;
+}
+
+
+/* Convert a given mask/val to a custom symbolic value */
+int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *cptr,
+                                 int mask,int val,
+                                 char *buf,unsigned int maxlen,
+                                 unsigned int *len)
+{
+       if (!cptr) return -EINVAL;
+       if (!cptr->info->val_to_sym) return -EINVAL;
+       return cptr->info->val_to_sym(cptr,mask,val,buf,maxlen,len);
+}
+
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *cptr,
+                                 const char *buf,unsigned int len,
+                                 int *maskptr,int *valptr)
+{
+       if (!cptr) return -EINVAL;
+       if (!cptr->info->sym_to_val) return -EINVAL;
+       return cptr->info->sym_to_val(cptr,buf,len,maskptr,valptr);
+}
+
+
+static unsigned int gen_bitmask_string(int msk,int val,int msk_only,
+                                      const char **names,
+                                      char *ptr,unsigned int len)
+{
+       unsigned int idx;
+       long sm,um;
+       int spcFl;
+       unsigned int uc,cnt;
+       const char *idStr;
+
+       spcFl = 0;
+       uc = 0;
+       um = 0;
+       for (idx = 0, sm = 1; msk; idx++, sm <<= 1) {
+               if (sm & msk) {
+                       msk &= ~sm;
+                       idStr = names[idx];
+                       if (idStr) {
+                               cnt = scnprintf(ptr,len,"%s%s%s",
+                                               (spcFl ? " " : ""),
+                                               (msk_only ? "" :
+                                                ((val & sm) ? "+" : "-")),
+                                               idStr);
+                               ptr += cnt; len -= cnt; uc += cnt;
+                               spcFl = !0;
+                       } else {
+                               um |= sm;
+                       }
+               }
+       }
+       if (um) {
+               if (msk_only) {
+                       cnt = scnprintf(ptr,len,"%s0x%lx",
+                                       (spcFl ? " " : ""),
+                                       um);
+                       ptr += cnt; len -= cnt; uc += cnt;
+                       spcFl = !0;
+               } else if (um & val) {
+                       cnt = scnprintf(ptr,len,"%s+0x%lx",
+                                       (spcFl ? " " : ""),
+                                       um & val);
+                       ptr += cnt; len -= cnt; uc += cnt;
+                       spcFl = !0;
+               } else if (um & ~val) {
+                       cnt = scnprintf(ptr,len,"%s+0x%lx",
+                                       (spcFl ? " " : ""),
+                                       um & ~val);
+                       ptr += cnt; len -= cnt; uc += cnt;
+                       spcFl = !0;
+               }
+       }
+       return uc;
+}
+
+
+static int parse_token(const char *ptr,unsigned int len,
+                      int *valptr,
+                      const char **names,unsigned int namecnt)
+{
+       char buf[33];
+       unsigned int slen;
+       unsigned int idx;
+       int negfl;
+       char *p2;
+       *valptr = 0;
+       if (!names) namecnt = 0;
+       for (idx = 0; idx < namecnt; idx++) {
+               if (!names[idx]) continue;
+               slen = strlen(names[idx]);
+               if (slen != len) continue;
+               if (memcmp(names[idx],ptr,slen)) continue;
+               *valptr = idx;
+               return 0;
+       }
+       negfl = 0;
+       if ((*ptr == '-') || (*ptr == '+')) {
+               negfl = (*ptr == '-');
+               ptr++; len--;
+       }
+       if (len >= sizeof(buf)) return -EINVAL;
+       memcpy(buf,ptr,len);
+       buf[len] = 0;
+       *valptr = simple_strtol(buf,&p2,0);
+       if (negfl) *valptr = -(*valptr);
+       if (*p2) return -EINVAL;
+       return 0;
+}
+
+
+static int parse_mtoken(const char *ptr,unsigned int len,
+                       int *valptr,
+                       const char **names,int valid_bits)
+{
+       char buf[33];
+       unsigned int slen;
+       unsigned int idx;
+       char *p2;
+       int msk;
+       *valptr = 0;
+       for (idx = 0, msk = 1; valid_bits; idx++, msk <<= 1) {
+               if (!msk & valid_bits) continue;
+               valid_bits &= ~msk;
+               if (!names[idx]) continue;
+               slen = strlen(names[idx]);
+               if (slen != len) continue;
+               if (memcmp(names[idx],ptr,slen)) continue;
+               *valptr = msk;
+               return 0;
+       }
+       if (len >= sizeof(buf)) return -EINVAL;
+       memcpy(buf,ptr,len);
+       buf[len] = 0;
+       *valptr = simple_strtol(buf,&p2,0);
+       if (*p2) return -EINVAL;
+       return 0;
+}
+
+
+static int parse_tlist(const char *ptr,unsigned int len,
+                      int *maskptr,int *valptr,
+                      const char **names,int valid_bits)
+{
+       unsigned int cnt;
+       int mask,val,kv,mode,ret;
+       mask = 0;
+       val = 0;
+       ret = 0;
+       while (len) {
+               cnt = 0;
+               while ((cnt < len) &&
+                      ((ptr[cnt] <= 32) ||
+                       (ptr[cnt] >= 127))) cnt++;
+               ptr += cnt;
+               len -= cnt;
+               mode = 0;
+               if ((*ptr == '-') || (*ptr == '+')) {
+                       mode = (*ptr == '-') ? -1 : 1;
+                       ptr++;
+                       len--;
+               }
+               cnt = 0;
+               while (cnt < len) {
+                       if (ptr[cnt] <= 32) break;
+                       if (ptr[cnt] >= 127) break;
+                       cnt++;
+               }
+               if (!cnt) break;
+               if (parse_mtoken(ptr,cnt,&kv,names,valid_bits)) {
+                       ret = -EINVAL;
+                       break;
+               }
+               ptr += cnt;
+               len -= cnt;
+               switch (mode) {
+               case 0:
+                       mask = valid_bits;
+                       val |= kv;
+                       break;
+               case -1:
+                       mask |= kv;
+                       val &= ~kv;
+                       break;
+               case 1:
+                       mask |= kv;
+                       val |= kv;
+                       break;
+               default:
+                       break;
+               }
+       }
+       *maskptr = mask;
+       *valptr = val;
+       return ret;
+}
+
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *cptr,
+                          const char *ptr,unsigned int len,
+                          int *maskptr,int *valptr)
+{
+       int ret = -EINVAL;
+       unsigned int cnt;
+
+       *maskptr = 0;
+       *valptr = 0;
+
+       cnt = 0;
+       while ((cnt < len) && ((ptr[cnt] <= 32) || (ptr[cnt] >= 127))) cnt++;
+       len -= cnt; ptr += cnt;
+       cnt = 0;
+       while ((cnt < len) && ((ptr[len-(cnt+1)] <= 32) ||
+                              (ptr[len-(cnt+1)] >= 127))) cnt++;
+       len -= cnt;
+
+       if (!len) return -EINVAL;
+
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               if (cptr->info->type == pvr2_ctl_int) {
+                       ret = parse_token(ptr,len,valptr,0,0);
+                       if ((ret == 0) &&
+                           ((*valptr < cptr->info->def.type_int.min_value) ||
+                            (*valptr > cptr->info->def.type_int.max_value))) {
+                               ret = -EINVAL;
+                       }
+                       if (maskptr) *maskptr = ~0;
+               } else if (cptr->info->type == pvr2_ctl_enum) {
+                       ret = parse_token(
+                               ptr,len,valptr,
+                               cptr->info->def.type_enum.value_names,
+                               cptr->info->def.type_enum.count);
+                       if ((ret == 0) &&
+                           ((*valptr < 0) ||
+                            (*valptr >= cptr->info->def.type_enum.count))) {
+                               ret = -EINVAL;
+                       }
+                       if (maskptr) *maskptr = ~0;
+               } else if (cptr->info->type == pvr2_ctl_bitmask) {
+                       ret = parse_tlist(
+                               ptr,len,maskptr,valptr,
+                               cptr->info->def.type_bitmask.bit_names,
+                               cptr->info->def.type_bitmask.valid_bits);
+               }
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *cptr,
+                                   int mask,int val,
+                                   char *buf,unsigned int maxlen,
+                                   unsigned int *len)
+{
+       int ret = -EINVAL;
+
+       *len = 0;
+       if (cptr->info->type == pvr2_ctl_int) {
+               *len = scnprintf(buf,maxlen,"%d",val);
+               ret = 0;
+       } else if (cptr->info->type == pvr2_ctl_enum) {
+               const char **names;
+               names = cptr->info->def.type_enum.value_names;
+               if ((val >= 0) &&
+                   (val < cptr->info->def.type_enum.count)) {
+                       if (names[val]) {
+                               *len = scnprintf(
+                                       buf,maxlen,"%s",
+                                       names[val]);
+                       } else {
+                               *len = 0;
+                       }
+                       ret = 0;
+               }
+       } else if (cptr->info->type == pvr2_ctl_bitmask) {
+               *len = gen_bitmask_string(
+                       val & mask & cptr->info->def.type_bitmask.valid_bits,
+                       ~0,!0,
+                       cptr->info->def.type_bitmask.bit_names,
+                       buf,maxlen);
+       }
+       return ret;
+}
+
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *cptr,
+                          int mask,int val,
+                          char *buf,unsigned int maxlen,
+                          unsigned int *len)
+{
+       int ret;
+       LOCK_TAKE(cptr->hdw->big_lock); do {
+               ret = pvr2_ctrl_value_to_sym_internal(cptr,mask,val,
+                                                     buf,maxlen,len);
+       } while(0); LOCK_GIVE(cptr->hdw->big_lock);
+       return ret;
+}
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-ctrl.h b/drivers/media/video/pvrusb2/pvrusb2-ctrl.h
new file mode 100644 (file)
index 0000000..9d74151
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_CTRL_H
+#define __PVRUSB2_CTRL_H
+
+struct pvr2_ctrl;
+
+enum pvr2_ctl_type {
+       pvr2_ctl_int = 0,
+       pvr2_ctl_enum = 1,
+       pvr2_ctl_bitmask = 2,
+};
+
+
+/* Set the given control. */
+int pvr2_ctrl_set_value(struct pvr2_ctrl *,int val);
+
+/* Set/clear specific bits of the given control. */
+int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *,int mask,int val);
+
+/* Get the current value of the given control. */
+int pvr2_ctrl_get_value(struct pvr2_ctrl *,int *valptr);
+
+/* Retrieve control's type */
+enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *);
+
+/* Retrieve control's maximum value (int type) */
+int pvr2_ctrl_get_max(struct pvr2_ctrl *);
+
+/* Retrieve control's minimum value (int type) */
+int pvr2_ctrl_get_min(struct pvr2_ctrl *);
+
+/* Retrieve control's default value (any type) */
+int pvr2_ctrl_get_def(struct pvr2_ctrl *);
+
+/* Retrieve control's enumeration count (enum only) */
+int pvr2_ctrl_get_cnt(struct pvr2_ctrl *);
+
+/* Retrieve control's valid mask bits (bit mask only) */
+int pvr2_ctrl_get_mask(struct pvr2_ctrl *);
+
+/* Retrieve the control's name */
+const char *pvr2_ctrl_get_name(struct pvr2_ctrl *);
+
+/* Retrieve the control's desc */
+const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *);
+
+/* Retrieve a control enumeration or bit mask value */
+int pvr2_ctrl_get_valname(struct pvr2_ctrl *,int,char *,unsigned int,
+                         unsigned int *);
+
+/* Return true if control is writable */
+int pvr2_ctrl_is_writable(struct pvr2_ctrl *);
+
+/* Return true if control has custom symbolic representation */
+int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *);
+
+/* Convert a given mask/val to a custom symbolic value */
+int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *,
+                                 int mask,int val,
+                                 char *buf,unsigned int maxlen,
+                                 unsigned int *len);
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *,
+                                 const char *buf,unsigned int len,
+                                 int *maskptr,int *valptr);
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *,
+                          int mask,int val,
+                          char *buf,unsigned int maxlen,
+                          unsigned int *len);
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *,
+                          const char *buf,unsigned int len,
+                          int *maskptr,int *valptr);
+
+/* Convert a given mask/val to a symbolic value - must already be
+   inside of critical region. */
+int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *,
+                          int mask,int val,
+                          char *buf,unsigned int maxlen,
+                          unsigned int *len);
+
+#endif /* __PVRUSB2_CTRL_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.c b/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.c
new file mode 100644 (file)
index 0000000..47e7f5d
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   cx2584x, in kernels 2.6.16 or newer.
+
+*/
+
+#include "pvrusb2-cx2584x-v4l.h"
+#include "pvrusb2-video-v4l.h"
+#include "pvrusb2-i2c-cmd-v4l2.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <media/cx25840.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+struct pvr2_v4l_cx2584x {
+       struct pvr2_i2c_handler handler;
+       struct pvr2_decoder_ctrl ctrl;
+       struct pvr2_i2c_client *client;
+       struct pvr2_hdw *hdw;
+       unsigned long stale_mask;
+};
+
+
+static void set_input(struct pvr2_v4l_cx2584x *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       struct v4l2_routing route;
+       enum cx25840_video_input vid_input;
+       enum cx25840_audio_input aud_input;
+
+       memset(&route,0,sizeof(route));
+
+       switch(hdw->input_val) {
+       case PVR2_CVAL_INPUT_TV:
+               vid_input = CX25840_COMPOSITE7;
+               aud_input = CX25840_AUDIO8;
+               break;
+       case PVR2_CVAL_INPUT_COMPOSITE:
+               vid_input = CX25840_COMPOSITE3;
+               aud_input = CX25840_AUDIO_SERIAL;
+               break;
+       case PVR2_CVAL_INPUT_SVIDEO:
+               vid_input = CX25840_SVIDEO1;
+               aud_input = CX25840_AUDIO_SERIAL;
+               break;
+       case PVR2_CVAL_INPUT_RADIO:
+       default:
+               // Just set it to be composite input for now...
+               vid_input = CX25840_COMPOSITE3;
+               aud_input = CX25840_AUDIO_SERIAL;
+               break;
+       }
+
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c cx2584x set_input vid=0x%x aud=0x%x",
+                  vid_input,aud_input);
+       route.input = (u32)vid_input;
+       pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_VIDEO_ROUTING,&route);
+       route.input = (u32)aud_input;
+       pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_AUDIO_ROUTING,&route);
+}
+
+
+static int check_input(struct pvr2_v4l_cx2584x *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       return hdw->input_dirty != 0;
+}
+
+
+static void set_audio(struct pvr2_v4l_cx2584x *ctxt)
+{
+       u32 val;
+       struct pvr2_hdw *hdw = ctxt->hdw;
+
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c cx2584x set_audio %d",
+                  hdw->srate_val);
+       switch (hdw->srate_val) {
+       default:
+       case PVR2_CVAL_SRATE_48:
+               val = 48000;
+               break;
+       case PVR2_CVAL_SRATE_44_1:
+               val = 44100;
+               break;
+       }
+       pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_AUDIO_CLOCK_FREQ,&val);
+}
+
+
+static int check_audio(struct pvr2_v4l_cx2584x *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       return hdw->srate_dirty != 0;
+}
+
+
+struct pvr2_v4l_cx2584x_ops {
+       void (*update)(struct pvr2_v4l_cx2584x *);
+       int (*check)(struct pvr2_v4l_cx2584x *);
+};
+
+
+static const struct pvr2_v4l_cx2584x_ops decoder_ops[] = {
+       { .update = set_input, .check = check_input},
+       { .update = set_audio, .check = check_audio},
+};
+
+
+static void decoder_detach(struct pvr2_v4l_cx2584x *ctxt)
+{
+       ctxt->client->handler = 0;
+       ctxt->hdw->decoder_ctrl = 0;
+       kfree(ctxt);
+}
+
+
+static int decoder_check(struct pvr2_v4l_cx2584x *ctxt)
+{
+       unsigned long msk;
+       unsigned int idx;
+
+       for (idx = 0; idx < sizeof(decoder_ops)/sizeof(decoder_ops[0]);
+            idx++) {
+               msk = 1 << idx;
+               if (ctxt->stale_mask & msk) continue;
+               if (decoder_ops[idx].check(ctxt)) {
+                       ctxt->stale_mask |= msk;
+               }
+       }
+       return ctxt->stale_mask != 0;
+}
+
+
+static void decoder_update(struct pvr2_v4l_cx2584x *ctxt)
+{
+       unsigned long msk;
+       unsigned int idx;
+
+       for (idx = 0; idx < sizeof(decoder_ops)/sizeof(decoder_ops[0]);
+            idx++) {
+               msk = 1 << idx;
+               if (!(ctxt->stale_mask & msk)) continue;
+               ctxt->stale_mask &= ~msk;
+               decoder_ops[idx].update(ctxt);
+       }
+}
+
+
+static void decoder_enable(struct pvr2_v4l_cx2584x *ctxt,int fl)
+{
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c cx25840 decoder_enable(%d)",fl);
+       pvr2_v4l2_cmd_stream(ctxt->client,fl);
+}
+
+
+static int decoder_detect(struct pvr2_i2c_client *cp)
+{
+       int ret;
+       /* Attempt to query the decoder - let's see if it will answer */
+       struct v4l2_queryctrl qc;
+
+       memset(&qc,0,sizeof(qc));
+
+       qc.id = V4L2_CID_BRIGHTNESS;
+
+       ret = pvr2_i2c_client_cmd(cp,VIDIOC_QUERYCTRL,&qc);
+       return ret == 0; /* Return true if it answered */
+}
+
+
+static int decoder_is_tuned(struct pvr2_v4l_cx2584x *ctxt)
+{
+       struct v4l2_tuner vt;
+       int ret;
+
+       memset(&vt,0,sizeof(vt));
+       ret = pvr2_i2c_client_cmd(ctxt->client,VIDIOC_G_TUNER,&vt);
+       if (ret < 0) return -EINVAL;
+       return vt.signal ? 1 : 0;
+}
+
+
+static unsigned int decoder_describe(struct pvr2_v4l_cx2584x *ctxt,
+                                    char *buf,unsigned int cnt)
+{
+       return scnprintf(buf,cnt,"handler: pvrusb2-cx2584x-v4l");
+}
+
+
+static void decoder_reset(struct pvr2_v4l_cx2584x *ctxt)
+{
+       int ret;
+       ret = pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_RESET,0);
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c cx25840 decoder_reset (ret=%d)",ret);
+}
+
+
+const static struct pvr2_i2c_handler_functions hfuncs = {
+       .detach = (void (*)(void *))decoder_detach,
+       .check = (int (*)(void *))decoder_check,
+       .update = (void (*)(void *))decoder_update,
+       .describe = (unsigned int (*)(void *,char *,unsigned int))decoder_describe,
+};
+
+
+int pvr2_i2c_cx2584x_v4l_setup(struct pvr2_hdw *hdw,
+                              struct pvr2_i2c_client *cp)
+{
+       struct pvr2_v4l_cx2584x *ctxt;
+
+       if (hdw->decoder_ctrl) return 0;
+       if (cp->handler) return 0;
+       if (!decoder_detect(cp)) return 0;
+
+       ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL);
+       if (!ctxt) return 0;
+       memset(ctxt,0,sizeof(*ctxt));
+
+       ctxt->handler.func_data = ctxt;
+       ctxt->handler.func_table = &hfuncs;
+       ctxt->ctrl.ctxt = ctxt;
+       ctxt->ctrl.detach = (void (*)(void *))decoder_detach;
+       ctxt->ctrl.enable = (void (*)(void *,int))decoder_enable;
+       ctxt->ctrl.tuned = (int (*)(void *))decoder_is_tuned;
+       ctxt->ctrl.force_reset = (void (*)(void*))decoder_reset;
+       ctxt->client = cp;
+       ctxt->hdw = hdw;
+       ctxt->stale_mask = (1 << (sizeof(decoder_ops)/
+                                 sizeof(decoder_ops[0]))) - 1;
+       hdw->decoder_ctrl = &ctxt->ctrl;
+       cp->handler = &ctxt->handler;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x cx2584x V4L2 handler set up",
+                  cp->client->addr);
+       return !0;
+}
+
+
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.h b/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.h
new file mode 100644 (file)
index 0000000..54b2844
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+#ifndef __PVRUSB2_CX2584X_V4L_H
+#define __PVRUSB2_CX2584X_V4L_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which handles combined device audio & video processing.
+   This interface is used internally by the driver; higher level code
+   should only interact through the interface provided by
+   pvrusb2-hdw.h.
+
+*/
+
+
+
+#include "pvrusb2-i2c-core.h"
+
+int pvr2_i2c_cx2584x_v4l_setup(struct pvr2_hdw *,struct pvr2_i2c_client *);
+
+
+#endif /* __PVRUSB2_CX2584X_V4L_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-debug.h b/drivers/media/video/pvrusb2/pvrusb2-debug.h
new file mode 100644 (file)
index 0000000..d95a858
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_DEBUG_H
+#define __PVRUSB2_DEBUG_H
+
+extern int pvrusb2_debug;
+
+#define pvr2_trace(msk, fmt, arg...) do {if(msk & pvrusb2_debug) printk(KERN_INFO "pvrusb2: " fmt "\n", ##arg); } while (0)
+
+/* These are listed in *rough* order of decreasing usefulness and
+   increasing noise level. */
+#define PVR2_TRACE_INFO       (1 <<  0) // Normal messages
+#define PVR2_TRACE_ERROR_LEGS (1 <<  1) // error messages
+#define PVR2_TRACE_TOLERANCE  (1 <<  2) // track tolerance-affected errors
+#define PVR2_TRACE_TRAP       (1 <<  3) // Trap & report misbehavior from app
+#define PVR2_TRACE_INIT       (1 <<  4) // misc initialization steps
+#define PVR2_TRACE_START_STOP (1 <<  5) // Streaming start / stop
+#define PVR2_TRACE_CTL        (1 <<  6) // commit of control changes
+#define PVR2_TRACE_DEBUG      (1 <<  7) // Temporary debug code
+#define PVR2_TRACE_EEPROM     (1 <<  8) // eeprom parsing / report
+#define PVR2_TRACE_STRUCT     (1 <<  9) // internal struct creation
+#define PVR2_TRACE_OPEN_CLOSE (1 << 10) // application open / close
+#define PVR2_TRACE_CREG       (1 << 11) // Main critical region entry / exit
+#define PVR2_TRACE_SYSFS      (1 << 12) // Sysfs driven I/O
+#define PVR2_TRACE_FIRMWARE   (1 << 13) // firmware upload actions
+#define PVR2_TRACE_CHIPS      (1 << 14) // chip broadcast operation
+#define PVR2_TRACE_I2C        (1 << 15) // I2C related stuff
+#define PVR2_TRACE_I2C_CMD    (1 << 16) // Software commands to I2C modules
+#define PVR2_TRACE_I2C_CORE   (1 << 17) // I2C core debugging
+#define PVR2_TRACE_I2C_TRAF   (1 << 18) // I2C traffic through the adapter
+#define PVR2_TRACE_V4LIOCTL   (1 << 19) // v4l ioctl details
+#define PVR2_TRACE_ENCODER    (1 << 20) // mpeg2 encoder operation
+#define PVR2_TRACE_BUF_POOL   (1 << 21) // Track buffer pool management
+#define PVR2_TRACE_BUF_FLOW   (1 << 22) // Track buffer flow in system
+#define PVR2_TRACE_DATA_FLOW  (1 << 23) // Track data flow
+#define PVR2_TRACE_DEBUGIFC   (1 << 24) // Debug interface actions
+#define PVR2_TRACE_GPIO       (1 << 25) // GPIO state bit changes
+
+
+#endif /* __PVRUSB2_HDW_INTERNAL_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-debugifc.c b/drivers/media/video/pvrusb2/pvrusb2-debugifc.c
new file mode 100644 (file)
index 0000000..586900e
--- /dev/null
@@ -0,0 +1,478 @@
+/*
+ *
+ *  $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 <linux/string.h>
+#include <linux/slab.h>
+#include "pvrusb2-debugifc.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-i2c-core.h"
+
+struct debugifc_mask_item {
+       const char *name;
+       unsigned long msk;
+};
+
+static struct debugifc_mask_item mask_items[] = {
+       {"ENC_FIRMWARE",(1<<PVR2_SUBSYS_B_ENC_FIRMWARE)},
+       {"ENC_CFG",(1<<PVR2_SUBSYS_B_ENC_CFG)},
+       {"DIG_RUN",(1<<PVR2_SUBSYS_B_DIGITIZER_RUN)},
+       {"USB_RUN",(1<<PVR2_SUBSYS_B_USBSTREAM_RUN)},
+       {"ENC_RUN",(1<<PVR2_SUBSYS_B_ENC_RUN)},
+};
+
+
+static unsigned int debugifc_count_whitespace(const char *buf,
+                                             unsigned int count)
+{
+       unsigned int scnt;
+       char ch;
+
+       for (scnt = 0; scnt < count; scnt++) {
+               ch = buf[scnt];
+               if (ch == ' ') continue;
+               if (ch == '\t') continue;
+               if (ch == '\n') continue;
+               break;
+       }
+       return scnt;
+}
+
+
+static unsigned int debugifc_count_nonwhitespace(const char *buf,
+                                                unsigned int count)
+{
+       unsigned int scnt;
+       char ch;
+
+       for (scnt = 0; scnt < count; scnt++) {
+               ch = buf[scnt];
+               if (ch == ' ') break;
+               if (ch == '\t') break;
+               if (ch == '\n') break;
+       }
+       return scnt;
+}
+
+
+static unsigned int debugifc_isolate_word(const char *buf,unsigned int count,
+                                         const char **wstrPtr,
+                                         unsigned int *wlenPtr)
+{
+       const char *wptr;
+       unsigned int consume_cnt = 0;
+       unsigned int wlen;
+       unsigned int scnt;
+
+       wptr = 0;
+       wlen = 0;
+       scnt = debugifc_count_whitespace(buf,count);
+       consume_cnt += scnt; count -= scnt; buf += scnt;
+       if (!count) goto done;
+
+       scnt = debugifc_count_nonwhitespace(buf,count);
+       if (!scnt) goto done;
+       wptr = buf;
+       wlen = scnt;
+       consume_cnt += scnt; count -= scnt; buf += scnt;
+
+ done:
+       *wstrPtr = wptr;
+       *wlenPtr = wlen;
+       return consume_cnt;
+}
+
+
+static int debugifc_parse_unsigned_number(const char *buf,unsigned int count,
+                                         u32 *num_ptr)
+{
+       u32 result = 0;
+       u32 val;
+       int ch;
+       int radix = 10;
+       if ((count >= 2) && (buf[0] == '0') &&
+           ((buf[1] == 'x') || (buf[1] == 'X'))) {
+               radix = 16;
+               count -= 2;
+               buf += 2;
+       } else if ((count >= 1) && (buf[0] == '0')) {
+               radix = 8;
+       }
+
+       while (count--) {
+               ch = *buf++;
+               if ((ch >= '0') && (ch <= '9')) {
+                       val = ch - '0';
+               } else if ((ch >= 'a') && (ch <= 'f')) {
+                       val = ch - 'a' + 10;
+               } else if ((ch >= 'A') && (ch <= 'F')) {
+                       val = ch - 'A' + 10;
+               } else {
+                       return -EINVAL;
+               }
+               if (val >= radix) return -EINVAL;
+               result *= radix;
+               result += val;
+       }
+       *num_ptr = result;
+       return 0;
+}
+
+
+static int debugifc_match_keyword(const char *buf,unsigned int count,
+                                 const char *keyword)
+{
+       unsigned int kl;
+       if (!keyword) return 0;
+       kl = strlen(keyword);
+       if (kl != count) return 0;
+       return !memcmp(buf,keyword,kl);
+}
+
+
+static unsigned long debugifc_find_mask(const char *buf,unsigned int count)
+{
+       struct debugifc_mask_item *mip;
+       unsigned int idx;
+       for (idx = 0; idx < sizeof(mask_items)/sizeof(mask_items[0]); idx++) {
+               mip = mask_items + idx;
+               if (debugifc_match_keyword(buf,count,mip->name)) {
+                       return mip->msk;
+               }
+       }
+       return 0;
+}
+
+
+static int debugifc_print_mask(char *buf,unsigned int sz,
+                              unsigned long msk,unsigned long val)
+{
+       struct debugifc_mask_item *mip;
+       unsigned int idx;
+       int bcnt = 0;
+       int ccnt;
+       for (idx = 0; idx < sizeof(mask_items)/sizeof(mask_items[0]); idx++) {
+               mip = mask_items + idx;
+               if (!(mip->msk & msk)) continue;
+               ccnt = scnprintf(buf,sz,"%s%c%s",
+                                (bcnt ? " " : ""),
+                                ((mip->msk & val) ? '+' : '-'),
+                                mip->name);
+               sz -= ccnt;
+               buf += ccnt;
+               bcnt += ccnt;
+       }
+       return bcnt;
+}
+
+static unsigned int debugifc_parse_subsys_mask(const char *buf,
+                                              unsigned int count,
+                                              unsigned long *mskPtr,
+                                              unsigned long *valPtr)
+{
+       const char *wptr;
+       unsigned int consume_cnt = 0;
+       unsigned int scnt;
+       unsigned int wlen;
+       int mode;
+       unsigned long m1,msk,val;
+
+       msk = 0;
+       val = 0;
+
+       while (count) {
+               scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+               if (!scnt) break;
+               consume_cnt += scnt; count -= scnt; buf += scnt;
+               if (!wptr) break;
+
+               mode = 0;
+               if (wlen) switch (wptr[0]) {
+               case '+':
+                       wptr++;
+                       wlen--;
+                       break;
+               case '-':
+                       mode = 1;
+                       wptr++;
+                       wlen--;
+                       break;
+               }
+               if (!wlen) continue;
+               m1 = debugifc_find_mask(wptr,wlen);
+               if (!m1) break;
+               msk |= m1;
+               if (!mode) val |= m1;
+       }
+       *mskPtr = msk;
+       *valPtr = val;
+       return consume_cnt;
+}
+
+
+int pvr2_debugifc_print_info(struct pvr2_hdw *hdw,char *buf,unsigned int acnt)
+{
+       int bcnt = 0;
+       int ccnt;
+       struct pvr2_hdw_debug_info dbg;
+
+       pvr2_hdw_get_debug_info(hdw,&dbg);
+
+       ccnt = scnprintf(buf,acnt,"big lock %s; ctl lock %s",
+                        (dbg.big_lock_held ? "held" : "free"),
+                        (dbg.ctl_lock_held ? "held" : "free"));
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       if (dbg.ctl_lock_held) {
+               ccnt = scnprintf(buf,acnt,"; cmd_state=%d cmd_code=%d"
+                                " cmd_wlen=%d cmd_rlen=%d"
+                                " wpend=%d rpend=%d tmout=%d rstatus=%d"
+                                " wstatus=%d",
+                                dbg.cmd_debug_state,dbg.cmd_code,
+                                dbg.cmd_debug_write_len,
+                                dbg.cmd_debug_read_len,
+                                dbg.cmd_debug_write_pend,
+                                dbg.cmd_debug_read_pend,
+                                dbg.cmd_debug_timeout,
+                                dbg.cmd_debug_rstatus,
+                                dbg.cmd_debug_wstatus);
+               bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       }
+       ccnt = scnprintf(buf,acnt,"\n");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(
+               buf,acnt,"driver flags: %s %s %s\n",
+               (dbg.flag_init_ok ? "initialized" : "uninitialized"),
+               (dbg.flag_ok ? "ok" : "fail"),
+               (dbg.flag_disconnected ? "disconnected" : "connected"));
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(buf,acnt,"Subsystems enabled / configured: ");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = debugifc_print_mask(buf,acnt,dbg.subsys_flags,dbg.subsys_flags);
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(buf,acnt,"\n");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(buf,acnt,"Subsystems disabled / unconfigured: ");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = debugifc_print_mask(buf,acnt,~dbg.subsys_flags,dbg.subsys_flags);
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(buf,acnt,"\n");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+       ccnt = scnprintf(buf,acnt,"Attached I2C modules:\n");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = pvr2_i2c_report(hdw,buf,acnt);
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+       return bcnt;
+}
+
+
+int pvr2_debugifc_print_status(struct pvr2_hdw *hdw,
+                              char *buf,unsigned int acnt)
+{
+       int bcnt = 0;
+       int ccnt;
+       unsigned long msk;
+       int ret;
+       u32 gpio_dir,gpio_in,gpio_out;
+
+       ret = pvr2_hdw_is_hsm(hdw);
+       ccnt = scnprintf(buf,acnt,"USB link speed: %s\n",
+                        (ret < 0 ? "FAIL" : (ret ? "high" : "full")));
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+       gpio_dir = 0; gpio_in = 0; gpio_out = 0;
+       pvr2_hdw_gpio_get_dir(hdw,&gpio_dir);
+       pvr2_hdw_gpio_get_out(hdw,&gpio_out);
+       pvr2_hdw_gpio_get_in(hdw,&gpio_in);
+       ccnt = scnprintf(buf,acnt,"GPIO state: dir=0x%x in=0x%x out=0x%x\n",
+                        gpio_dir,gpio_in,gpio_out);
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+       ccnt = scnprintf(buf,acnt,"Streaming is %s\n",
+                        pvr2_hdw_get_streaming(hdw) ? "on" : "off");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+       msk = pvr2_hdw_subsys_get(hdw);
+       ccnt = scnprintf(buf,acnt,"Subsystems enabled / configured: ");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = debugifc_print_mask(buf,acnt,msk,msk);
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(buf,acnt,"\n");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(buf,acnt,"Subsystems disabled / unconfigured: ");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = debugifc_print_mask(buf,acnt,~msk,msk);
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(buf,acnt,"\n");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+       msk = pvr2_hdw_subsys_stream_get(hdw);
+       ccnt = scnprintf(buf,acnt,"Subsystems stopped on stream shutdown: ");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = debugifc_print_mask(buf,acnt,msk,msk);
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+       ccnt = scnprintf(buf,acnt,"\n");
+       bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+       return bcnt;
+}
+
+
+int pvr2_debugifc_do1cmd(struct pvr2_hdw *hdw,const char *buf,
+                        unsigned int count)
+{
+       const char *wptr;
+       unsigned int wlen;
+       unsigned int scnt;
+
+       scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+       if (!scnt) return 0;
+       count -= scnt; buf += scnt;
+       if (!wptr) return 0;
+
+       pvr2_trace(PVR2_TRACE_DEBUGIFC,"debugifc cmd: \"%.*s\"",wlen,wptr);
+       if (debugifc_match_keyword(wptr,wlen,"reset")) {
+               scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+               if (!scnt) return -EINVAL;
+               count -= scnt; buf += scnt;
+               if (!wptr) return -EINVAL;
+               if (debugifc_match_keyword(wptr,wlen,"cpu")) {
+                       pvr2_hdw_cpureset_assert(hdw,!0);
+                       pvr2_hdw_cpureset_assert(hdw,0);
+                       return 0;
+               } else if (debugifc_match_keyword(wptr,wlen,"bus")) {
+                       pvr2_hdw_device_reset(hdw);
+               } else if (debugifc_match_keyword(wptr,wlen,"soft")) {
+                       return pvr2_hdw_cmd_powerup(hdw);
+               } else if (debugifc_match_keyword(wptr,wlen,"deep")) {
+                       return pvr2_hdw_cmd_deep_reset(hdw);
+               } else if (debugifc_match_keyword(wptr,wlen,"firmware")) {
+                       return pvr2_upload_firmware2(hdw);
+               } else if (debugifc_match_keyword(wptr,wlen,"decoder")) {
+                       return pvr2_hdw_cmd_decoder_reset(hdw);
+               }
+               return -EINVAL;
+       } else if (debugifc_match_keyword(wptr,wlen,"subsys_flags")) {
+               unsigned long msk = 0;
+               unsigned long val = 0;
+               if (debugifc_parse_subsys_mask(buf,count,&msk,&val) != count) {
+                       pvr2_trace(PVR2_TRACE_DEBUGIFC,
+                                  "debugifc parse error on subsys mask");
+                       return -EINVAL;
+               }
+               pvr2_hdw_subsys_bit_chg(hdw,msk,val);
+               return 0;
+       } else if (debugifc_match_keyword(wptr,wlen,"stream_flags")) {
+               unsigned long msk = 0;
+               unsigned long val = 0;
+               if (debugifc_parse_subsys_mask(buf,count,&msk,&val) != count) {
+                       pvr2_trace(PVR2_TRACE_DEBUGIFC,
+                                  "debugifc parse error on stream mask");
+                       return -EINVAL;
+               }
+               pvr2_hdw_subsys_stream_bit_chg(hdw,msk,val);
+               return 0;
+       } else if (debugifc_match_keyword(wptr,wlen,"cpufw")) {
+               scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+               if (!scnt) return -EINVAL;
+               count -= scnt; buf += scnt;
+               if (!wptr) return -EINVAL;
+               if (debugifc_match_keyword(wptr,wlen,"fetch")) {
+                       pvr2_hdw_cpufw_set_enabled(hdw,!0);
+                       return 0;
+               } else if (debugifc_match_keyword(wptr,wlen,"done")) {
+                       pvr2_hdw_cpufw_set_enabled(hdw,0);
+                       return 0;
+               } else {
+                       return -EINVAL;
+               }
+       } else if (debugifc_match_keyword(wptr,wlen,"gpio")) {
+               int dir_fl = 0;
+               int ret;
+               u32 msk,val;
+               scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+               if (!scnt) return -EINVAL;
+               count -= scnt; buf += scnt;
+               if (!wptr) return -EINVAL;
+               if (debugifc_match_keyword(wptr,wlen,"dir")) {
+                       dir_fl = !0;
+               } else if (!debugifc_match_keyword(wptr,wlen,"out")) {
+                       return -EINVAL;
+               }
+               scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+               if (!scnt) return -EINVAL;
+               count -= scnt; buf += scnt;
+               if (!wptr) return -EINVAL;
+               ret = debugifc_parse_unsigned_number(wptr,wlen,&msk);
+               if (ret) return ret;
+               scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+               if (wptr) {
+                       ret = debugifc_parse_unsigned_number(wptr,wlen,&val);
+                       if (ret) return ret;
+               } else {
+                       val = msk;
+                       msk = 0xffffffff;
+               }
+               if (dir_fl) {
+                       ret = pvr2_hdw_gpio_chg_dir(hdw,msk,val);
+               } else {
+                       ret = pvr2_hdw_gpio_chg_out(hdw,msk,val);
+               }
+               return ret;
+       }
+       pvr2_trace(PVR2_TRACE_DEBUGIFC,
+                  "debugifc failed to recognize cmd: \"%.*s\"",wlen,wptr);
+       return -EINVAL;
+}
+
+
+int pvr2_debugifc_docmd(struct pvr2_hdw *hdw,const char *buf,
+                       unsigned int count)
+{
+       unsigned int bcnt = 0;
+       int ret;
+
+       while (count) {
+               for (bcnt = 0; bcnt < count; bcnt++) {
+                       if (buf[bcnt] == '\n') break;
+               }
+
+               ret = pvr2_debugifc_do1cmd(hdw,buf,bcnt);
+               if (ret < 0) return ret;
+               if (bcnt < count) bcnt++;
+               buf += bcnt;
+               count -= bcnt;
+       }
+
+       return 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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-debugifc.h b/drivers/media/video/pvrusb2/pvrusb2-debugifc.h
new file mode 100644 (file)
index 0000000..990b02d
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_DEBUGIFC_H
+#define __PVRUSB2_DEBUGIFC_H
+
+struct pvr2_hdw;
+
+/* Non-intrusively print some useful debugging info from inside the
+   driver.  This should work even if the driver appears to be
+   wedged. */
+int pvr2_debugifc_print_info(struct pvr2_hdw *,
+                            char *buf_ptr,unsigned int buf_size);
+
+/* Print general status of driver.  This will also trigger a probe of
+   the USB link.  Unlike print_info(), this one synchronizes with the
+   driver so the information should be self-consistent (but it will
+   hang if the driver is wedged). */
+int pvr2_debugifc_print_status(struct pvr2_hdw *,
+                              char *buf_ptr,unsigned int buf_size);
+
+/* Parse a string command into a driver action. */
+int pvr2_debugifc_docmd(struct pvr2_hdw *,
+                       const char *buf_ptr,unsigned int buf_size);
+
+#endif /* __PVRUSB2_DEBUGIFC_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-demod.c b/drivers/media/video/pvrusb2/pvrusb2-demod.c
new file mode 100644 (file)
index 0000000..9686569
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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.h"
+#include "pvrusb2-util.h"
+#include "pvrusb2-demod.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+
+
+struct pvr2_demod_handler {
+       struct pvr2_hdw *hdw;
+       struct pvr2_i2c_client *client;
+       struct pvr2_i2c_handler i2c_handler;
+       int type_update_fl;
+};
+
+
+static void set_config(struct pvr2_demod_handler *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       int cfg = 0;
+
+       switch (hdw->tuner_type) {
+       case TUNER_PHILIPS_FM1216ME_MK3:
+       case TUNER_PHILIPS_FM1236_MK3:
+               cfg = TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE;
+               break;
+       default:
+               break;
+       }
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c demod set_config(0x%x)",cfg);
+       pvr2_i2c_client_cmd(ctxt->client,TDA9887_SET_CONFIG,&cfg);
+       ctxt->type_update_fl = 0;
+}
+
+
+static int demod_check(struct pvr2_demod_handler *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       if (hdw->tuner_updated) ctxt->type_update_fl = !0;
+       return ctxt->type_update_fl != 0;
+}
+
+
+static void demod_update(struct pvr2_demod_handler *ctxt)
+{
+       if (ctxt->type_update_fl) set_config(ctxt);
+}
+
+
+static void demod_detach(struct pvr2_demod_handler *ctxt)
+{
+       ctxt->client->handler = 0;
+       kfree(ctxt);
+}
+
+
+static unsigned int demod_describe(struct pvr2_demod_handler *ctxt,char *buf,unsigned int cnt)
+{
+       return scnprintf(buf,cnt,"handler: pvrusb2-demod");
+}
+
+
+const static struct pvr2_i2c_handler_functions tuner_funcs = {
+       .detach = (void (*)(void *))demod_detach,
+       .check = (int (*)(void *))demod_check,
+       .update = (void (*)(void *))demod_update,
+       .describe = (unsigned int (*)(void *,char *,unsigned int))demod_describe,
+};
+
+
+int pvr2_i2c_demod_setup(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp)
+{
+       struct pvr2_demod_handler *ctxt;
+       if (cp->handler) return 0;
+
+       ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL);
+       if (!ctxt) return 0;
+       memset(ctxt,0,sizeof(*ctxt));
+
+       ctxt->i2c_handler.func_data = ctxt;
+       ctxt->i2c_handler.func_table = &tuner_funcs;
+       ctxt->type_update_fl = !0;
+       ctxt->client = cp;
+       ctxt->hdw = hdw;
+       cp->handler = &ctxt->i2c_handler;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x tda9887 V4L2 handler set up",
+                  cp->client->addr);
+       return !0;
+}
+
+
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-demod.h b/drivers/media/video/pvrusb2/pvrusb2-demod.h
new file mode 100644 (file)
index 0000000..4c4e40f
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_DEMOD_H
+#define __PVRUSB2_DEMOD_H
+
+#include "pvrusb2-i2c-core.h"
+
+int pvr2_i2c_demod_setup(struct pvr2_hdw *,struct pvr2_i2c_client *);
+
+#endif /* __PVRUSB2_DEMOD_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-eeprom.c b/drivers/media/video/pvrusb2/pvrusb2-eeprom.c
new file mode 100644 (file)
index 0000000..94d383f
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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-eeprom.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+
+#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__)
+
+
+
+/*
+
+   Read and analyze data in the eeprom.  Use tveeprom to figure out
+   the packet structure, since this is another Hauppauge device and
+   internally it has a family resemblence to ivtv-type devices
+
+*/
+
+#include <media/tveeprom.h>
+
+/* We seem to only be interested in the last 128 bytes of the EEPROM */
+#define EEPROM_SIZE 128
+
+/* Grab EEPROM contents, needed for direct method. */
+static u8 *pvr2_eeprom_fetch(struct pvr2_hdw *hdw)
+{
+       struct i2c_msg msg[2];
+       u8 *eeprom;
+       u8 iadd[2];
+       u8 addr;
+       u16 eepromSize;
+       unsigned int offs;
+       int ret;
+       int mode16 = 0;
+       unsigned pcnt,tcnt;
+       eeprom = kmalloc(EEPROM_SIZE,GFP_KERNEL);
+       if (!eeprom) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Failed to allocate memory"
+                          " required to read eeprom");
+               return 0;
+       }
+
+       trace_eeprom("Value for eeprom addr from controller was 0x%x",
+                    hdw->eeprom_addr);
+       addr = hdw->eeprom_addr;
+       /* Seems that if the high bit is set, then the *real* eeprom
+          address is shifted right now bit position (noticed this in
+          newer PVR USB2 hardware) */
+       if (addr & 0x80) addr >>= 1;
+
+       /* FX2 documentation states that a 16bit-addressed eeprom is
+          expected if the I2C address is an odd number (yeah, this is
+          strange but it's what they do) */
+       mode16 = (addr & 1);
+       eepromSize = (mode16 ? 4096 : 256);
+       trace_eeprom("Examining %d byte eeprom at location 0x%x"
+                    " using %d bit addressing",eepromSize,addr,
+                    mode16 ? 16 : 8);
+
+       msg[0].addr = addr;
+       msg[0].flags = 0;
+       msg[0].len = mode16 ? 2 : 1;
+       msg[0].buf = iadd;
+       msg[1].addr = addr;
+       msg[1].flags = I2C_M_RD;
+
+       /* We have to do the actual eeprom data fetch ourselves, because
+          (1) we're only fetching part of the eeprom, and (2) if we were
+          getting the whole thing our I2C driver can't grab it in one
+          pass - which is what tveeprom is otherwise going to attempt */
+       memset(eeprom,0,EEPROM_SIZE);
+       for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) {
+               pcnt = 16;
+               if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt;
+               offs = tcnt + (eepromSize - EEPROM_SIZE);
+               if (mode16) {
+                       iadd[0] = offs >> 8;
+                       iadd[1] = offs;
+               } else {
+                       iadd[0] = offs;
+               }
+               msg[1].len = pcnt;
+               msg[1].buf = eeprom+tcnt;
+               if ((ret = i2c_transfer(
+                            &hdw->i2c_adap,
+                            msg,sizeof(msg)/sizeof(msg[0]))) != 2) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "eeprom fetch set offs err=%d",ret);
+                       kfree(eeprom);
+                       return 0;
+               }
+       }
+       return eeprom;
+}
+
+
+/* Directly call eeprom analysis function within tveeprom. */
+int pvr2_eeprom_analyze(struct pvr2_hdw *hdw)
+{
+       u8 *eeprom;
+       struct tveeprom tvdata;
+
+       memset(&tvdata,0,sizeof(tvdata));
+
+       eeprom = pvr2_eeprom_fetch(hdw);
+       if (!eeprom) return -EINVAL;
+
+       {
+               struct i2c_client fake_client;
+               /* Newer version expects a useless client interface */
+               fake_client.addr = hdw->eeprom_addr;
+               fake_client.adapter = &hdw->i2c_adap;
+               tveeprom_hauppauge_analog(&fake_client,&tvdata,eeprom);
+       }
+
+       trace_eeprom("eeprom assumed v4l tveeprom module");
+       trace_eeprom("eeprom direct call results:");
+       trace_eeprom("has_radio=%d",tvdata.has_radio);
+       trace_eeprom("tuner_type=%d",tvdata.tuner_type);
+       trace_eeprom("tuner_formats=0x%x",tvdata.tuner_formats);
+       trace_eeprom("audio_processor=%d",tvdata.audio_processor);
+       trace_eeprom("model=%d",tvdata.model);
+       trace_eeprom("revision=%d",tvdata.revision);
+       trace_eeprom("serial_number=%d",tvdata.serial_number);
+       trace_eeprom("rev_str=%s",tvdata.rev_str);
+       hdw->tuner_type = tvdata.tuner_type;
+       hdw->serial_number = tvdata.serial_number;
+       hdw->std_mask_eeprom = tvdata.tuner_formats;
+
+       kfree(eeprom);
+
+       return 0;
+}
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-eeprom.h b/drivers/media/video/pvrusb2/pvrusb2-eeprom.h
new file mode 100644 (file)
index 0000000..8424297
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+#ifndef __PVRUSB2_EEPROM_H
+#define __PVRUSB2_EEPROM_H
+
+struct pvr2_hdw;
+
+int pvr2_eeprom_analyze(struct pvr2_hdw *);
+
+#endif /* __PVRUSB2_EEPROM_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-encoder.c b/drivers/media/video/pvrusb2/pvrusb2-encoder.c
new file mode 100644 (file)
index 0000000..0917fe3
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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 <linux/device.h>   // for linux/firmware.h
+#include <linux/firmware.h>
+#include <linux/videodev2.h>
+#include <media/cx2341x.h>
+#include "pvrusb2-util.h"
+#include "pvrusb2-encoder.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+
+static u32 pvr_tbl_emphasis [] = {
+       [PVR2_CVAL_AUDIOEMPHASIS_NONE] = 0x0 << 12,
+       [PVR2_CVAL_AUDIOEMPHASIS_50_15] = 0x1 << 12,
+       [PVR2_CVAL_AUDIOEMPHASIS_CCITT] = 0x3 << 12,
+};
+
+static u32 pvr_tbl_srate[] = {
+       [PVR2_CVAL_SRATE_48] =  0x01,
+       [PVR2_CVAL_SRATE_44_1] = 0x00,
+};
+
+static u32 pvr_tbl_audiobitrate[] = {
+       [PVR2_CVAL_AUDIOBITRATE_384] = 0xe << 4,
+       [PVR2_CVAL_AUDIOBITRATE_320] = 0xd << 4,
+       [PVR2_CVAL_AUDIOBITRATE_256] = 0xc << 4,
+       [PVR2_CVAL_AUDIOBITRATE_224] = 0xb << 4,
+       [PVR2_CVAL_AUDIOBITRATE_192] = 0xa << 4,
+       [PVR2_CVAL_AUDIOBITRATE_160] = 0x9 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_128] = 0x8 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_112] = 0x7 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_96]  = 0x6 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_80]  = 0x5 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_64]  = 0x4 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_56]  = 0x3 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_48]  = 0x2 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_32]  = 0x1 << 4,
+       [PVR2_CVAL_AUDIOBITRATE_VBR] = 0x0 << 4,
+};
+
+
+/* Firmware mailbox flags - definitions found from ivtv */
+#define IVTV_MBOX_FIRMWARE_DONE 0x00000004
+#define IVTV_MBOX_DRIVER_DONE 0x00000002
+#define IVTV_MBOX_DRIVER_BUSY 0x00000001
+
+
+static int pvr2_write_encoder_words(struct pvr2_hdw *hdw,
+                                   const u32 *data, unsigned int dlen)
+{
+       unsigned int idx;
+       int ret;
+       unsigned int offs = 0;
+       unsigned int chunkCnt;
+
+       /*
+
+       Format: First byte must be 0x01.  Remaining 32 bit words are
+       spread out into chunks of 7 bytes each, little-endian ordered,
+       offset at zero within each 2 blank bytes following and a
+       single byte that is 0x44 plus the offset of the word.  Repeat
+       request for additional words, with offset adjusted
+       accordingly.
+
+       */
+       while (dlen) {
+               chunkCnt = 8;
+               if (chunkCnt > dlen) chunkCnt = dlen;
+               memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer));
+               hdw->cmd_buffer[0] = 0x01;
+               for (idx = 0; idx < chunkCnt; idx++) {
+                       hdw->cmd_buffer[1+(idx*7)+6] = 0x44 + idx + offs;
+                       PVR2_DECOMPOSE_LE(hdw->cmd_buffer, 1+(idx*7),
+                                         data[idx]);
+               }
+               ret = pvr2_send_request(hdw,
+                                       hdw->cmd_buffer,1+(chunkCnt*7),
+                                       0,0);
+               if (ret) return ret;
+               data += chunkCnt;
+               dlen -= chunkCnt;
+               offs += chunkCnt;
+       }
+
+       return 0;
+}
+
+
+static int pvr2_read_encoder_words(struct pvr2_hdw *hdw,int statusFl,
+                                  u32 *data, unsigned int dlen)
+{
+       unsigned int idx;
+       int ret;
+       unsigned int offs = 0;
+       unsigned int chunkCnt;
+
+       /*
+
+       Format: First byte must be 0x02 (status check) or 0x28 (read
+       back block of 32 bit words).  Next 6 bytes must be zero,
+       followed by a single byte of 0x44+offset for portion to be
+       read.  Returned data is packed set of 32 bits words that were
+       read.
+
+       */
+
+       while (dlen) {
+               chunkCnt = 16;
+               if (chunkCnt > dlen) chunkCnt = dlen;
+               memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer));
+               hdw->cmd_buffer[0] = statusFl ? 0x02 : 0x28;
+               hdw->cmd_buffer[7] = 0x44 + offs;
+               ret = pvr2_send_request(hdw,
+                                       hdw->cmd_buffer,8,
+                                       hdw->cmd_buffer,chunkCnt * 4);
+               if (ret) return ret;
+
+               for (idx = 0; idx < chunkCnt; idx++) {
+                       data[idx] = PVR2_COMPOSE_LE(hdw->cmd_buffer,idx*4);
+               }
+               data += chunkCnt;
+               dlen -= chunkCnt;
+               offs += chunkCnt;
+       }
+
+       return 0;
+}
+
+
+static int pvr2_write_encoder_vcmd (struct pvr2_hdw *hdw, u8 cmd,
+                                   int args, ...)
+{
+       unsigned int poll_count;
+       int ret = 0;
+       va_list vl;
+       unsigned int idx;
+       u32 wrData[16];
+       u32 rdData[32];
+
+       /*
+
+       The encoder seems to speak entirely using blocks 32 bit words.
+       In ivtv driver terms, this is a mailbox which we populate with
+       data and watch what the hardware does with it.  The first word
+       is a set of flags used to control the transaction, the second
+       word is the command to execute, the third byte is zero (ivtv
+       driver suggests that this is some kind of return value), and
+       the fourth byte is a specified timeout (windows driver always
+       uses 0x00060000 except for one case when it is zero).  All
+       successive words are the argument words for the command.
+
+       First, write out the entire set of words, with the first word
+       being zero.
+
+       Next, write out just the first word again, but set it to
+       IVTV_MBOX_DRIVER_DONE | IVTV_DRIVER_BUSY this time (which
+       probably means "go").
+
+       Next, read back 16 words as status.  Check the first word,
+       which should have IVTV_MBOX_FIRMWARE_DONE set.  If however
+       that bit is not set, then the command isn't done so repeat the
+       read.
+
+       Next, read back 32 words and compare with the original
+       arugments.  Hopefully they will match.
+
+       Finally, write out just the first word again, but set it to
+       0x0 this time (which probably means "idle").
+
+       */
+
+
+       LOCK_TAKE(hdw->ctl_lock); do {
+
+               wrData[0] = 0;
+               wrData[1] = cmd;
+               wrData[2] = 0;
+               wrData[3] = 0x00060000;
+               va_start(vl, args);
+               for (idx = 0; idx < args; idx++) {
+                       wrData[idx+4] = va_arg(vl, u32);
+               }
+               va_end(vl);
+               args += 4;
+               while (args < sizeof(wrData)/sizeof(wrData[0])) {
+                       wrData[args++] = 0;
+               }
+
+               ret = pvr2_write_encoder_words(hdw,wrData,args);
+               if (ret) break;
+               wrData[0] = IVTV_MBOX_DRIVER_DONE|IVTV_MBOX_DRIVER_BUSY;
+               ret = pvr2_write_encoder_words(hdw,wrData,1);
+               if (ret) break;
+               poll_count = 0;
+               while (1) {
+                       if (poll_count < 10000000) poll_count++;
+                       ret = pvr2_read_encoder_words(hdw,!0,rdData,1);
+                       if (ret) break;
+                       if (rdData[0] & IVTV_MBOX_FIRMWARE_DONE) {
+                               break;
+                       }
+                       if (poll_count == 100) {
+                               pvr2_trace(
+                                       PVR2_TRACE_ERROR_LEGS,
+                                       "***WARNING*** device's encoder"
+                                       " appears to be stuck"
+                                       " (status=0%08x)",rdData[0]);
+                               pvr2_trace(
+                                       PVR2_TRACE_ERROR_LEGS,
+                                       "Encoder command: 0x%02x",cmd);
+                               for (idx = 4; idx < args; idx++) {
+                                       pvr2_trace(
+                                               PVR2_TRACE_ERROR_LEGS,
+                                               "Encoder arg%d: 0x%08x",
+                                               idx-3,wrData[idx]);
+                               }
+                               pvr2_trace(
+                                       PVR2_TRACE_ERROR_LEGS,
+                                       "Giving up waiting."
+                                       "  It is likely that"
+                                       " this is a bad idea...");
+                               ret = -EBUSY;
+                               break;
+                       }
+               }
+               if (ret) break;
+               wrData[0] = 0x7;
+               ret = pvr2_read_encoder_words(hdw,0,rdData,16);
+               if (ret) break;
+               for (idx = 0; idx < args; idx++) {
+                       if (rdData[idx] != wrData[idx]) {
+                               pvr2_trace(
+                                       PVR2_TRACE_DEBUG,
+                                       "pvr2_encoder idx %02x mismatch exp:"
+                                       " %08x got: %08x",
+                                       idx,wrData[idx],rdData[idx]);
+                       }
+               }
+
+               wrData[0] = 0x0;
+               ret = pvr2_write_encoder_words(hdw,wrData,1);
+               if (ret) break;
+
+       } while(0); LOCK_GIVE(hdw->ctl_lock);
+
+       return ret;
+}
+
+int pvr2_encoder_configure(struct pvr2_hdw *hdw)
+{
+       int ret = 0, audio, i;
+       v4l2_std_id vd_std = hdw->std_mask_cur;
+       int height = hdw->res_ver_val;
+       int width = hdw->res_hor_val;
+       int height_full = !hdw->interlace_val;
+
+       int is_30fps, is_ntsc;
+
+       if (vd_std & V4L2_STD_NTSC) {
+               is_ntsc=1;
+               is_30fps=1;
+       } else if (vd_std & V4L2_STD_PAL_M) {
+               is_ntsc=0;
+               is_30fps=1;
+       } else {
+               is_ntsc=0;
+               is_30fps=0;
+       }
+
+       pvr2_trace(PVR2_TRACE_ENCODER,"pvr2_encoder_configure");
+
+       /* set stream output port.  Some notes here: The ivtv-derived
+          encoder documentation says that this command only gets a
+          single argument.  However the Windows driver for the model
+          29xxx series hardware has been sending 0x01 as a second
+          argument, while the Windows driver for the model 24xxx
+          series hardware has been sending 0x02 as a second argument.
+          Confusing matters further are the observations that 0x01
+          for that second argument simply won't work on the 24xxx
+          hardware, while 0x02 will work on the 29xxx - except that
+          when we use 0x02 then xawtv breaks due to a loss of
+          synchronization with the mpeg packet headers.  While xawtv
+          should be fixed to let it resync better (I did try to
+          contact Gerd about this but he has not answered), it has
+          also been determined that sending 0x00 as this mystery
+          second argument seems to work on both hardware models AND
+          xawtv works again.  So we're going to send 0x00. */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_OUTPUT_PORT, 2,
+                                      0x01, 0x00);
+
+       /* set the Program Index Information. We want I,P,B frames (max 400) */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_PGM_INDEX_INFO, 2,
+                                      0x07, 0x0190);
+
+       /* NOTE : windows driver sends these */
+       /* Mike Isely <isely@pobox.com> 7-Mar-2006 The windows driver
+          sends the following commands but if we do the same then
+          many apps are no longer able to read the video stream.
+          Leaving these out seems to do no harm at all, so they're
+          commented out for that reason. */
+#ifdef notdef
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 5,0,0,0);
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 3,1,0,0);
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 8,0,0,0);
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 4,1,0,0);
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 0,3,0,0);
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_MISC,4,15,0,0,0);
+#endif
+
+       /* Strange compared to ivtv data. */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2,
+                                      0xf0, 0xf0);
+
+       /* setup firmware to notify us about some events (don't know why...) */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_EVENT_NOTIFICATION, 4,
+                                      0, 0, 0x10000000, 0xffffffff);
+
+       /* set fps to 25 or 30 (1 or 0)*/
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_FRAME_RATE, 1,
+                                      is_30fps ? 0 : 1);
+
+       /* set encoding resolution */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_FRAME_SIZE, 2,
+                                      (height_full ? height : (height / 2)),
+                                      width);
+       /* set encoding aspect ratio to 4:3 */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_ASPECT_RATIO, 1,
+                                      0x02);
+
+       /* VBI */
+
+       if (hdw->config == pvr2_config_vbi) {
+               int lines = 2 * (is_30fps ? 12 : 18);
+               int size = (4*((lines*1443+3)/4)) / lines;
+               ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_VBI_CONFIG, 7,
+                                              0xbd05, 1, 4,
+                                              0x25256262, 0x387f7f7f,
+                                              lines , size);
+//                                     0x25256262, 0x13135454, lines , size);
+               /* select vbi lines */
+#define line_used(l)  (is_30fps ? (l >= 10 && l <= 21) : (l >= 6 && l <= 23))
+               for (i = 2 ; i <= 24 ; i++){
+                       ret |= pvr2_write_encoder_vcmd(
+                               hdw,CX2341X_ENC_SET_VBI_LINE, 5,
+                               i-1,line_used(i), 0, 0, 0);
+                       ret |= pvr2_write_encoder_vcmd(
+                               hdw,CX2341X_ENC_SET_VBI_LINE, 5,
+                               (i-1) | (1 << 31),
+                               line_used(i), 0, 0, 0);
+               }
+       } else {
+               ret |= pvr2_write_encoder_vcmd(
+                       hdw,CX2341X_ENC_SET_VBI_LINE, 5,
+                       0xffffffff,0,0,0,0);
+       }
+
+       /* set stream type, depending on resolution. */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_STREAM_TYPE, 1,
+                                      height_full ? 0x0a : 0x0b);
+       /* set video bitrate */
+       ret |= pvr2_write_encoder_vcmd(
+               hdw, CX2341X_ENC_SET_BIT_RATE, 3,
+               (hdw->vbr_val ? 1 : 0),
+               hdw->videobitrate_val,
+               hdw->videopeak_val / 400);
+       /* setup GOP structure (GOP size = 0f or 0c, 3-1 = 2 B-frames) */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_GOP_PROPERTIES, 2,
+                                      is_30fps ?  0x0f : 0x0c, 0x03);
+
+       /* enable 3:2 pulldown */
+       ret |= pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_SET_3_2_PULLDOWN,1,0);
+
+       /* set GOP open/close property (open) */
+       ret |= pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_SET_GOP_CLOSURE,1,0);
+
+       /* set audio stream properties 0x40b9? 0100 0000 1011 1001 */
+       audio = (pvr_tbl_audiobitrate[hdw->audiobitrate_val] |
+                pvr_tbl_srate[hdw->srate_val] |
+                hdw->audiolayer_val << 2 |
+                (hdw->audiocrc_val ? 1 << 14 : 0) |
+                pvr_tbl_emphasis[hdw->audioemphasis_val]);
+
+       ret |= pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_SET_AUDIO_PROPERTIES,1,
+                                      audio);
+
+       /* set dynamic noise reduction filter to manual, Horiz/Vert */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_DNR_FILTER_MODE, 2,
+                                      0, 0x03);
+
+       /* dynamic noise reduction filter param */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_DNR_FILTER_PROPS, 2
+                                      , 0, 0);
+
+       /* dynamic noise reduction median filter */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_CORING_LEVELS, 4,
+                                      0, 0xff, 0, 0xff);
+
+       /* spacial prefiler parameter */
+       ret |= pvr2_write_encoder_vcmd(hdw,
+                                      CX2341X_ENC_SET_SPATIAL_FILTER_TYPE, 2,
+                                      0x01, 0x01);
+
+       /* initialize video input */
+       ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_INITIALIZE_INPUT, 0);
+
+       if (!ret) {
+               hdw->subsys_enabled_mask |= (1<<PVR2_SUBSYS_B_ENC_CFG);
+       }
+
+       return ret;
+}
+
+int pvr2_encoder_start(struct pvr2_hdw *hdw)
+{
+       int status;
+
+       /* unmask some interrupts */
+       pvr2_write_register(hdw, 0x0048, 0xbfffffff);
+
+       /* change some GPIO data */
+       pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000481);
+       pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000000);
+
+       if (hdw->config == pvr2_config_vbi) {
+               status = pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+                                                0x01,0x14);
+       } else if (hdw->config == pvr2_config_mpeg) {
+               status = pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+                                                0,0x13);
+       } else {
+               status = pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+                                                0,0x13);
+       }
+       if (!status) {
+               hdw->subsys_enabled_mask |= (1<<PVR2_SUBSYS_B_ENC_RUN);
+       }
+       return status;
+}
+
+int pvr2_encoder_stop(struct pvr2_hdw *hdw)
+{
+       int status;
+
+       /* mask all interrupts */
+       pvr2_write_register(hdw, 0x0048, 0xffffffff);
+
+       if (hdw->config == pvr2_config_vbi) {
+               status = pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+                                                0x01,0x01,0x14);
+       } else if (hdw->config == pvr2_config_mpeg) {
+               status = pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+                                                0x01,0,0x13);
+       } else {
+               status = pvr2_write_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+                                                0x01,0,0x13);
+       }
+
+       /* change some GPIO data */
+       /* Note: Bit d7 of dir appears to control the LED.  So we shut it
+          off here. */
+       pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000401);
+       pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000000);
+
+       if (!status) {
+               hdw->subsys_enabled_mask &= ~(1<<PVR2_SUBSYS_B_ENC_RUN);
+       }
+       return status;
+}
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-encoder.h b/drivers/media/video/pvrusb2/pvrusb2-encoder.h
new file mode 100644 (file)
index 0000000..01b5a0b
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+#ifndef __PVRUSB2_ENCODER_H
+#define __PVRUSB2_ENCODER_H
+
+struct pvr2_hdw;
+
+int pvr2_encoder_configure(struct pvr2_hdw *);
+int pvr2_encoder_start(struct pvr2_hdw *);
+int pvr2_encoder_stop(struct pvr2_hdw *);
+
+#endif /* __PVRUSB2_ENCODER_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h
new file mode 100644 (file)
index 0000000..217bbe5
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_HDW_INTERNAL_H
+#define __PVRUSB2_HDW_INTERNAL_H
+
+/*
+
+  This header sets up all the internal structures and definitions needed to
+  track and coordinate the driver's interaction with the hardware.  ONLY
+  source files which actually implement part of that whole circus should be
+  including this header.  Higher levels, like the external layers to the
+  various public APIs (V4L, sysfs, etc) should NOT ever include this
+  private, internal header.  This means that pvrusb2-hdw, pvrusb2-encoder,
+  etc will include this, but pvrusb2-v4l should not.
+
+*/
+
+#include <linux/config.h>
+#include <linux/videodev2.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-io.h"
+
+/* Legal values for the SRATE state variable */
+#define PVR2_CVAL_SRATE_48 0
+#define PVR2_CVAL_SRATE_44_1 1
+
+/* Legal values for the AUDIOBITRATE state variable */
+#define PVR2_CVAL_AUDIOBITRATE_384 0
+#define PVR2_CVAL_AUDIOBITRATE_320 1
+#define PVR2_CVAL_AUDIOBITRATE_256 2
+#define PVR2_CVAL_AUDIOBITRATE_224 3
+#define PVR2_CVAL_AUDIOBITRATE_192 4
+#define PVR2_CVAL_AUDIOBITRATE_160 5
+#define PVR2_CVAL_AUDIOBITRATE_128 6
+#define PVR2_CVAL_AUDIOBITRATE_112 7
+#define PVR2_CVAL_AUDIOBITRATE_96 8
+#define PVR2_CVAL_AUDIOBITRATE_80 9
+#define PVR2_CVAL_AUDIOBITRATE_64 10
+#define PVR2_CVAL_AUDIOBITRATE_56 11
+#define PVR2_CVAL_AUDIOBITRATE_48 12
+#define PVR2_CVAL_AUDIOBITRATE_32 13
+#define PVR2_CVAL_AUDIOBITRATE_VBR 14
+
+/* Legal values for the AUDIOEMPHASIS state variable */
+#define PVR2_CVAL_AUDIOEMPHASIS_NONE 0
+#define PVR2_CVAL_AUDIOEMPHASIS_50_15 1
+#define PVR2_CVAL_AUDIOEMPHASIS_CCITT 2
+
+/* Legal values for PVR2_CID_HSM */
+#define PVR2_CVAL_HSM_FAIL 0
+#define PVR2_CVAL_HSM_FULL 1
+#define PVR2_CVAL_HSM_HIGH 2
+
+#define PVR2_VID_ENDPOINT        0x84
+#define PVR2_UNK_ENDPOINT        0x86    /* maybe raw yuv ? */
+#define PVR2_VBI_ENDPOINT        0x88
+
+#define PVR2_CTL_BUFFSIZE        64
+
+#define FREQTABLE_SIZE 500
+
+#define LOCK_TAKE(x) do { mutex_lock(&x##_mutex); x##_held = !0; } while (0)
+#define LOCK_GIVE(x) do { x##_held = 0; mutex_unlock(&x##_mutex); } while (0)
+
+struct pvr2_decoder;
+
+typedef int (*pvr2_ctlf_is_dirty)(struct pvr2_ctrl *);
+typedef void (*pvr2_ctlf_clear_dirty)(struct pvr2_ctrl *);
+typedef int (*pvr2_ctlf_get_value)(struct pvr2_ctrl *,int *);
+typedef int (*pvr2_ctlf_set_value)(struct pvr2_ctrl *,int msk,int val);
+typedef int (*pvr2_ctlf_val_to_sym)(struct pvr2_ctrl *,int msk,int val,
+                                   char *,unsigned int,unsigned int *);
+typedef int (*pvr2_ctlf_sym_to_val)(struct pvr2_ctrl *,
+                                   const char *,unsigned int,
+                                   int *mskp,int *valp);
+
+/* This structure describes a specific control.  A table of these is set up
+   in pvrusb2-hdw.c. */
+struct pvr2_ctl_info {
+       /* Control's name suitable for use as an identifier */
+       const char *name;
+
+       /* Short description of control */
+       const char *desc;
+
+       /* Control's implementation */
+       pvr2_ctlf_get_value get_value;      /* Get its value */
+       pvr2_ctlf_set_value set_value;      /* Set its value */
+       pvr2_ctlf_val_to_sym val_to_sym;    /* Custom convert value->symbol */
+       pvr2_ctlf_sym_to_val sym_to_val;    /* Custom convert symbol->value */
+       pvr2_ctlf_is_dirty is_dirty;        /* Return true if dirty */
+       pvr2_ctlf_clear_dirty clear_dirty;  /* Clear dirty state */
+
+       /* Control's type (int, enum, bitmask) */
+       enum pvr2_ctl_type type;
+
+       /* Associated V4L control ID, if any */
+       int v4l_id;
+
+       /* Associated driver internal ID, if any */
+       int internal_id;
+
+       /* Don't implicitly initialize this control's value */
+       int skip_init;
+
+       /* Starting value for this control */
+       int default_value;
+
+       /* Type-specific control information */
+       union {
+               struct { /* Integer control */
+                       long min_value; /* lower limit */
+                       long max_value; /* upper limit */
+               } type_int;
+               struct { /* enumerated control */
+                       unsigned int count;       /* enum value count */
+                       const char **value_names; /* symbol names */
+               } type_enum;
+               struct { /* bitmask control */
+                       unsigned int valid_bits; /* bits in use */
+                       const char **bit_names;  /* symbol name/bit */
+               } type_bitmask;
+       } def;
+};
+
+
+struct pvr2_ctrl {
+       const struct pvr2_ctl_info *info;
+       struct pvr2_hdw *hdw;
+};
+
+
+struct pvr2_audio_stat {
+       void *ctxt;
+       void (*detach)(void *);
+       int (*status)(void *);
+};
+
+struct pvr2_decoder_ctrl {
+       void *ctxt;
+       void (*detach)(void *);
+       void (*enable)(void *,int);
+       int (*tuned)(void *);
+       void (*force_reset)(void *);
+};
+
+#define PVR2_I2C_PEND_DETECT  0x01  /* Need to detect a client type */
+#define PVR2_I2C_PEND_CLIENT  0x02  /* Client needs a specific update */
+#define PVR2_I2C_PEND_REFRESH 0x04  /* Client has specific pending bits */
+#define PVR2_I2C_PEND_STALE   0x08  /* Broadcast pending bits */
+
+#define PVR2_I2C_PEND_ALL (PVR2_I2C_PEND_DETECT |\
+                          PVR2_I2C_PEND_CLIENT |\
+                          PVR2_I2C_PEND_REFRESH |\
+                          PVR2_I2C_PEND_STALE)
+
+/* Disposition of firmware1 loading situation */
+#define FW1_STATE_UNKNOWN 0
+#define FW1_STATE_MISSING 1
+#define FW1_STATE_FAILED 2
+#define FW1_STATE_RELOAD 3
+#define FW1_STATE_OK 4
+
+/* Known major hardware variants, keyed from device ID */
+#define PVR2_HDW_TYPE_29XXX 0
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+#define PVR2_HDW_TYPE_24XXX 1
+#endif
+
+typedef int (*pvr2_i2c_func)(struct pvr2_hdw *,u8,u8 *,u16,u8 *, u16);
+#define PVR2_I2C_FUNC_CNT 128
+
+/* This structure contains all state data directly needed to
+   manipulate the hardware (as opposed to complying with a kernel
+   interface) */
+struct pvr2_hdw {
+       /* Underlying USB device handle */
+       struct usb_device *usb_dev;
+       struct usb_interface *usb_intf;
+
+       /* Device type, one of PVR2_HDW_TYPE_xxxxx */
+       unsigned int hdw_type;
+
+       /* Video spigot */
+       struct pvr2_stream *vid_stream;
+
+       /* Mutex for all hardware state control */
+       struct mutex big_lock_mutex;
+       int big_lock_held;  /* For debugging */
+
+       void (*poll_trigger_func)(void *);
+       void *poll_trigger_data;
+
+       char name[32];
+
+       /* I2C stuff */
+       struct i2c_adapter i2c_adap;
+       struct i2c_algorithm i2c_algo;
+       pvr2_i2c_func i2c_func[PVR2_I2C_FUNC_CNT];
+       int i2c_cx25840_hack_state;
+       int i2c_linked;
+       unsigned int i2c_pend_types;    /* Which types of update are needed */
+       unsigned long i2c_pend_mask;    /* Change bits we need to scan */
+       unsigned long i2c_stale_mask;   /* Pending broadcast change bits */
+       unsigned long i2c_active_mask;  /* All change bits currently in use */
+       struct list_head i2c_clients;
+       struct mutex i2c_list_lock;
+
+       /* Frequency table */
+       unsigned int freqTable[FREQTABLE_SIZE];
+       unsigned int freqProgSlot;
+       unsigned int freqSlot;
+
+       /* Stuff for handling low level control interaction with device */
+       struct mutex ctl_lock_mutex;
+       int ctl_lock_held;  /* For debugging */
+       struct urb *ctl_write_urb;
+       struct urb *ctl_read_urb;
+       unsigned char *ctl_write_buffer;
+       unsigned char *ctl_read_buffer;
+       volatile int ctl_write_pend_flag;
+       volatile int ctl_read_pend_flag;
+       volatile int ctl_timeout_flag;
+       struct completion ctl_done;
+       unsigned char cmd_buffer[PVR2_CTL_BUFFSIZE];
+       int cmd_debug_state;               // Low level command debugging info
+       unsigned char cmd_debug_code;      //
+       unsigned int cmd_debug_write_len;  //
+       unsigned int cmd_debug_read_len;   //
+
+       int flag_ok;            // device in known good state
+       int flag_disconnected;  // flag_ok == 0 due to disconnect
+       int flag_init_ok;       // true if structure is fully initialized
+       int flag_streaming_enabled; // true if streaming should be on
+       int fw1_state;          // current situation with fw1
+
+       int flag_decoder_is_tuned;
+
+       struct pvr2_decoder_ctrl *decoder_ctrl;
+
+       // CPU firmware info (used to help find / save firmware data)
+       char *fw_buffer;
+       unsigned int fw_size;
+
+       // Which subsystem pieces have been enabled / configured
+       unsigned long subsys_enabled_mask;
+
+       // Which subsystems are manipulated to enable streaming
+       unsigned long subsys_stream_mask;
+
+       // True if there is a request to trigger logging of state in each
+       // module.
+       int log_requested;
+
+       /* Tuner / frequency control stuff */
+       unsigned int tuner_type;
+       int tuner_updated;
+       unsigned int freqVal;
+       int freqDirty;
+
+       /* Video standard handling */
+       v4l2_std_id std_mask_eeprom; // Hardware supported selections
+       v4l2_std_id std_mask_avail;  // Which standards we may select from
+       v4l2_std_id std_mask_cur;    // Currently selected standard(s)
+       unsigned int std_enum_cnt;   // # of enumerated standards
+       int std_enum_cur;            // selected standard enumeration value
+       int std_dirty;               // True if std_mask_cur has changed
+       struct pvr2_ctl_info std_info_enum;
+       struct pvr2_ctl_info std_info_avail;
+       struct pvr2_ctl_info std_info_cur;
+       struct v4l2_standard *std_defs;
+       const char **std_enum_names;
+
+       // Generated string names, one per actual V4L2 standard
+       const char *std_mask_ptrs[32];
+       char std_mask_names[32][10];
+
+       int unit_number;             /* ID for driver instance */
+       unsigned long serial_number; /* ID for hardware itself */
+
+       /* Minor number used by v4l logic (yes, this is a hack, as there should
+          be no v4l junk here).  Probably a better way to do this. */
+       int v4l_minor_number;
+
+       /* Location of eeprom or a negative number if none */
+       int eeprom_addr;
+
+       enum pvr2_config config;
+
+       /* Information about what audio signal we're hearing */
+       int flag_stereo;
+       int flag_bilingual;
+       struct pvr2_audio_stat *audio_stat;
+
+       /* Control state */
+#define VCREATE_DATA(lab) int lab##_val; int lab##_dirty
+       VCREATE_DATA(brightness);
+       VCREATE_DATA(contrast);
+       VCREATE_DATA(saturation);
+       VCREATE_DATA(hue);
+       VCREATE_DATA(volume);
+       VCREATE_DATA(balance);
+       VCREATE_DATA(bass);
+       VCREATE_DATA(treble);
+       VCREATE_DATA(mute);
+       VCREATE_DATA(srate);
+       VCREATE_DATA(audiobitrate);
+       VCREATE_DATA(audiocrc);
+       VCREATE_DATA(audioemphasis);
+       VCREATE_DATA(vbr);
+       VCREATE_DATA(videobitrate);
+       VCREATE_DATA(videopeak);
+       VCREATE_DATA(input);
+       VCREATE_DATA(audiomode);
+       VCREATE_DATA(res_hor);
+       VCREATE_DATA(res_ver);
+       VCREATE_DATA(interlace);
+       VCREATE_DATA(audiolayer);
+#undef VCREATE_DATA
+
+       struct pvr2_ctrl *controls;
+};
+
+int pvr2_hdw_commit_ctl_internal(struct pvr2_hdw *hdw);
+
+unsigned int pvr2_hdw_get_signal_status_internal(struct pvr2_hdw *);
+
+void pvr2_hdw_subsys_bit_chg_no_lock(struct pvr2_hdw *hdw,
+                                    unsigned long msk,unsigned long val);
+void pvr2_hdw_subsys_stream_bit_chg_no_lock(struct pvr2_hdw *hdw,
+                                           unsigned long msk,
+                                           unsigned long val);
+
+void pvr2_hdw_internal_find_stdenum(struct pvr2_hdw *hdw);
+void pvr2_hdw_internal_set_std_avail(struct pvr2_hdw *hdw);
+
+int pvr2_i2c_basic_op(struct pvr2_hdw *,u8 i2c_addr,
+                     u8 *wdata,u16 wlen,
+                     u8 *rdata,u16 rlen);
+
+#endif /* __PVRUSB2_HDW_INTERNAL_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.c b/drivers/media/video/pvrusb2/pvrusb2-hdw.c
new file mode 100644 (file)
index 0000000..ae2038b
--- /dev/null
@@ -0,0 +1,2949 @@
+/*
+ *
+ *  $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 <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <asm/semaphore.h>
+#include <linux/videodev2.h>
+#include <media/cx2341x.h>
+#include "pvrusb2.h"
+#include "pvrusb2-std.h"
+#include "pvrusb2-util.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-i2c-core.h"
+#include "pvrusb2-tuner.h"
+#include "pvrusb2-eeprom.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-encoder.h"
+#include "pvrusb2-debug.h"
+
+struct usb_device_id pvr2_device_table[] = {
+       [PVR2_HDW_TYPE_29XXX] = { USB_DEVICE(0x2040, 0x2900) },
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+       [PVR2_HDW_TYPE_24XXX] = { USB_DEVICE(0x2040, 0x2400) },
+#endif
+       { }
+};
+
+MODULE_DEVICE_TABLE(usb, pvr2_device_table);
+
+static const char *pvr2_device_names[] = {
+       [PVR2_HDW_TYPE_29XXX] = "WinTV PVR USB2 Model Category 29xxxx",
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+       [PVR2_HDW_TYPE_24XXX] = "WinTV PVR USB2 Model Category 24xxxx",
+#endif
+};
+
+struct pvr2_string_table {
+       const char **lst;
+       unsigned int cnt;
+};
+
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+// Names of other client modules to request for 24xxx model hardware
+static const char *pvr2_client_24xxx[] = {
+       "cx25840",
+       "tuner",
+       "tda9887",
+       "wm8775",
+};
+#endif
+
+// Names of other client modules to request for 29xxx model hardware
+static const char *pvr2_client_29xxx[] = {
+       "msp3400",
+       "saa7115",
+       "tuner",
+       "tda9887",
+};
+
+static struct pvr2_string_table pvr2_client_lists[] = {
+       [PVR2_HDW_TYPE_29XXX] = {
+               pvr2_client_29xxx,
+               sizeof(pvr2_client_29xxx)/sizeof(pvr2_client_29xxx[0]),
+       },
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+       [PVR2_HDW_TYPE_24XXX] = {
+               pvr2_client_24xxx,
+               sizeof(pvr2_client_24xxx)/sizeof(pvr2_client_24xxx[0]),
+       },
+#endif
+};
+
+static struct pvr2_hdw *unit_pointers[PVR_NUM] = {[ 0 ... PVR_NUM-1 ] = 0};
+DECLARE_MUTEX(pvr2_unit_sem);
+
+static int ctlchg = 0;
+static int initusbreset = 1;
+static int procreload = 0;
+static int tuner[PVR_NUM] = { [0 ... PVR_NUM-1] = -1 };
+static int tolerance[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 };
+static int video_std[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 };
+static int init_pause_msec = 0;
+
+module_param(ctlchg, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(ctlchg, "0=optimize ctl change 1=always accept new ctl value");
+module_param(init_pause_msec, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(init_pause_msec, "hardware initialization settling delay");
+module_param(initusbreset, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(initusbreset, "Do USB reset device on probe");
+module_param(procreload, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(procreload,
+                "Attempt init failure recovery with firmware reload");
+module_param_array(tuner,    int, NULL, 0444);
+MODULE_PARM_DESC(tuner,"specify installed tuner type");
+module_param_array(video_std,    int, NULL, 0444);
+MODULE_PARM_DESC(video_std,"specify initial video standard");
+module_param_array(tolerance,    int, NULL, 0444);
+MODULE_PARM_DESC(tolerance,"specify stream error tolerance");
+
+#define PVR2_CTL_WRITE_ENDPOINT  0x01
+#define PVR2_CTL_READ_ENDPOINT   0x81
+
+#define PVR2_GPIO_IN 0x9008
+#define PVR2_GPIO_OUT 0x900c
+#define PVR2_GPIO_DIR 0x9020
+
+#define trace_firmware(...) pvr2_trace(PVR2_TRACE_FIRMWARE,__VA_ARGS__)
+
+#define PVR2_FIRMWARE_ENDPOINT   0x02
+
+/* size of a firmware chunk */
+#define FIRMWARE_CHUNK_SIZE 0x2000
+
+static const char *control_values_srate[] = {
+       [PVR2_CVAL_SRATE_48]   = "48KHz",
+       [PVR2_CVAL_SRATE_44_1] = "44.1KHz",
+};
+
+
+static const char *control_values_audiobitrate[] = {
+       [PVR2_CVAL_AUDIOBITRATE_384] = "384kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_320] = "320kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_256] = "256kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_224] = "224kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_192] = "192kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_160] = "160kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_128] = "128kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_112] = "112kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_96]  = "96kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_80]  = "80kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_64]  = "64kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_56]  = "56kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_48]  = "48kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_32]  = "32kb/s",
+       [PVR2_CVAL_AUDIOBITRATE_VBR] = "VBR",
+};
+
+
+static const char *control_values_audioemphasis[] = {
+       [PVR2_CVAL_AUDIOEMPHASIS_NONE]  = "None",
+       [PVR2_CVAL_AUDIOEMPHASIS_50_15] = "50/15us",
+       [PVR2_CVAL_AUDIOEMPHASIS_CCITT] = "CCITT J.17",
+};
+
+
+static const char *control_values_input[] = {
+       [PVR2_CVAL_INPUT_TV]        = "television",  /*xawtv needs this name*/
+       [PVR2_CVAL_INPUT_RADIO]     = "radio",
+       [PVR2_CVAL_INPUT_SVIDEO]    = "s-video",
+       [PVR2_CVAL_INPUT_COMPOSITE] = "composite",
+};
+
+
+static const char *control_values_audiomode[] = {
+       [V4L2_TUNER_MODE_MONO]   = "Mono",
+       [V4L2_TUNER_MODE_STEREO] = "Stereo",
+       [V4L2_TUNER_MODE_LANG1]  = "Lang1",
+       [V4L2_TUNER_MODE_LANG2]  = "Lang2",
+       [V4L2_TUNER_MODE_LANG1_LANG2] = "Lang1+Lang2",
+};
+
+
+static const char *control_values_hsm[] = {
+       [PVR2_CVAL_HSM_FAIL] = "Fail",
+       [PVR2_CVAL_HSM_HIGH] = "High",
+       [PVR2_CVAL_HSM_FULL] = "Full",
+};
+
+
+static const char *control_values_subsystem[] = {
+       [PVR2_SUBSYS_B_ENC_FIRMWARE]  = "enc_firmware",
+       [PVR2_SUBSYS_B_ENC_CFG] = "enc_config",
+       [PVR2_SUBSYS_B_DIGITIZER_RUN] = "digitizer_run",
+       [PVR2_SUBSYS_B_USBSTREAM_RUN] = "usbstream_run",
+       [PVR2_SUBSYS_B_ENC_RUN] = "enc_run",
+};
+
+
+static int ctrl_channelfreq_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       struct pvr2_hdw *hdw = cptr->hdw;
+       if ((hdw->freqProgSlot > 0) && (hdw->freqProgSlot <= FREQTABLE_SIZE)) {
+               *vp = hdw->freqTable[hdw->freqProgSlot-1];
+       } else {
+               *vp = 0;
+       }
+       return 0;
+}
+
+static int ctrl_channelfreq_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       struct pvr2_hdw *hdw = cptr->hdw;
+       if ((hdw->freqProgSlot > 0) && (hdw->freqProgSlot <= FREQTABLE_SIZE)) {
+               hdw->freqTable[hdw->freqProgSlot-1] = v;
+       }
+       return 0;
+}
+
+static int ctrl_channelprog_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->freqProgSlot;
+       return 0;
+}
+
+static int ctrl_channelprog_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       struct pvr2_hdw *hdw = cptr->hdw;
+       if ((v >= 0) && (v <= FREQTABLE_SIZE)) {
+               hdw->freqProgSlot = v;
+       }
+       return 0;
+}
+
+static int ctrl_channel_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->freqSlot;
+       return 0;
+}
+
+static int ctrl_channel_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       unsigned freq = 0;
+       struct pvr2_hdw *hdw = cptr->hdw;
+       hdw->freqSlot = v;
+       if ((hdw->freqSlot > 0) && (hdw->freqSlot <= FREQTABLE_SIZE)) {
+               freq = hdw->freqTable[hdw->freqSlot-1];
+       }
+       if (freq && (freq != hdw->freqVal)) {
+               hdw->freqVal = freq;
+               hdw->freqDirty = !0;
+       }
+       return 0;
+}
+
+static int ctrl_freq_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->freqVal;
+       return 0;
+}
+
+static int ctrl_freq_is_dirty(struct pvr2_ctrl *cptr)
+{
+       return cptr->hdw->freqDirty != 0;
+}
+
+static void ctrl_freq_clear_dirty(struct pvr2_ctrl *cptr)
+{
+       cptr->hdw->freqDirty = 0;
+}
+
+static int ctrl_freq_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       struct pvr2_hdw *hdw = cptr->hdw;
+       hdw->freqVal = v;
+       hdw->freqDirty = !0;
+       hdw->freqSlot = 0;
+       return 0;
+}
+
+static int ctrl_streamingenabled_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->flag_streaming_enabled;
+       return 0;
+}
+
+static int ctrl_hsm_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       int result = pvr2_hdw_is_hsm(cptr->hdw);
+       *vp = PVR2_CVAL_HSM_FULL;
+       if (result < 0) *vp = PVR2_CVAL_HSM_FAIL;
+       if (result) *vp = PVR2_CVAL_HSM_HIGH;
+       return 0;
+}
+
+static int ctrl_stdavail_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->std_mask_avail;
+       return 0;
+}
+
+static int ctrl_stdavail_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       struct pvr2_hdw *hdw = cptr->hdw;
+       v4l2_std_id ns;
+       ns = hdw->std_mask_avail;
+       ns = (ns & ~m) | (v & m);
+       if (ns == hdw->std_mask_avail) return 0;
+       hdw->std_mask_avail = ns;
+       pvr2_hdw_internal_set_std_avail(hdw);
+       pvr2_hdw_internal_find_stdenum(hdw);
+       return 0;
+}
+
+static int ctrl_std_val_to_sym(struct pvr2_ctrl *cptr,int msk,int val,
+                              char *bufPtr,unsigned int bufSize,
+                              unsigned int *len)
+{
+       *len = pvr2_std_id_to_str(bufPtr,bufSize,msk & val);
+       return 0;
+}
+
+static int ctrl_std_sym_to_val(struct pvr2_ctrl *cptr,
+                              const char *bufPtr,unsigned int bufSize,
+                              int *mskp,int *valp)
+{
+       int ret;
+       v4l2_std_id id;
+       ret = pvr2_std_str_to_id(&id,bufPtr,bufSize);
+       if (ret < 0) return ret;
+       if (mskp) *mskp = id;
+       if (valp) *valp = id;
+       return 0;
+}
+
+static int ctrl_stdcur_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->std_mask_cur;
+       return 0;
+}
+
+static int ctrl_stdcur_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       struct pvr2_hdw *hdw = cptr->hdw;
+       v4l2_std_id ns;
+       ns = hdw->std_mask_cur;
+       ns = (ns & ~m) | (v & m);
+       if (ns == hdw->std_mask_cur) return 0;
+       hdw->std_mask_cur = ns;
+       hdw->std_dirty = !0;
+       pvr2_hdw_internal_find_stdenum(hdw);
+       return 0;
+}
+
+static int ctrl_stdcur_is_dirty(struct pvr2_ctrl *cptr)
+{
+       return cptr->hdw->std_dirty != 0;
+}
+
+static void ctrl_stdcur_clear_dirty(struct pvr2_ctrl *cptr)
+{
+       cptr->hdw->std_dirty = 0;
+}
+
+static int ctrl_signal_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = ((pvr2_hdw_get_signal_status_internal(cptr->hdw) &
+               PVR2_SIGNAL_OK) ? 1 : 0);
+       return 0;
+}
+
+static int ctrl_subsys_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->subsys_enabled_mask;
+       return 0;
+}
+
+static int ctrl_subsys_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       pvr2_hdw_subsys_bit_chg_no_lock(cptr->hdw,m,v);
+       return 0;
+}
+
+static int ctrl_subsys_stream_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->subsys_stream_mask;
+       return 0;
+}
+
+static int ctrl_subsys_stream_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       pvr2_hdw_subsys_stream_bit_chg_no_lock(cptr->hdw,m,v);
+       return 0;
+}
+
+static int ctrl_stdenumcur_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+       struct pvr2_hdw *hdw = cptr->hdw;
+       if (v < 0) return -EINVAL;
+       if (v > hdw->std_enum_cnt) return -EINVAL;
+       hdw->std_enum_cur = v;
+       if (!v) return 0;
+       v--;
+       if (hdw->std_mask_cur == hdw->std_defs[v].id) return 0;
+       hdw->std_mask_cur = hdw->std_defs[v].id;
+       hdw->std_dirty = !0;
+       return 0;
+}
+
+
+static int ctrl_stdenumcur_get(struct pvr2_ctrl *cptr,int *vp)
+{
+       *vp = cptr->hdw->std_enum_cur;
+       return 0;
+}
+
+
+static int ctrl_stdenumcur_is_dirty(struct pvr2_ctrl *cptr)
+{
+       return cptr->hdw->std_dirty != 0;
+}
+
+
+static void ctrl_stdenumcur_clear_dirty(struct pvr2_ctrl *cptr)
+{
+       cptr->hdw->std_dirty = 0;
+}
+
+
+#define DEFINT(vmin,vmax) \
+       .type = pvr2_ctl_int, \
+       .def.type_int.min_value = vmin, \
+       .def.type_int.max_value = vmax
+
+#define DEFENUM(tab) \
+       .type = pvr2_ctl_enum, \
+       .def.type_enum.count = (sizeof(tab)/sizeof((tab)[0])), \
+       .def.type_enum.value_names = tab
+
+#define DEFMASK(msk,tab) \
+       .type = pvr2_ctl_bitmask, \
+       .def.type_bitmask.valid_bits = msk, \
+       .def.type_bitmask.bit_names = tab
+
+#define DEFREF(vname) \
+       .set_value = ctrl_set_##vname, \
+       .get_value = ctrl_get_##vname, \
+       .is_dirty = ctrl_isdirty_##vname, \
+       .clear_dirty = ctrl_cleardirty_##vname
+
+
+#define VCREATE_FUNCS(vname) \
+static int ctrl_get_##vname(struct pvr2_ctrl *cptr,int *vp) \
+{*vp = cptr->hdw->vname##_val; return 0;} \
+static int ctrl_set_##vname(struct pvr2_ctrl *cptr,int m,int v) \
+{cptr->hdw->vname##_val = v; cptr->hdw->vname##_dirty = !0; return 0;} \
+static int ctrl_isdirty_##vname(struct pvr2_ctrl *cptr) \
+{return cptr->hdw->vname##_dirty != 0;} \
+static void ctrl_cleardirty_##vname(struct pvr2_ctrl *cptr) \
+{cptr->hdw->vname##_dirty = 0;}
+
+VCREATE_FUNCS(brightness)
+VCREATE_FUNCS(contrast)
+VCREATE_FUNCS(saturation)
+VCREATE_FUNCS(hue)
+VCREATE_FUNCS(volume)
+VCREATE_FUNCS(balance)
+VCREATE_FUNCS(bass)
+VCREATE_FUNCS(treble)
+VCREATE_FUNCS(mute)
+VCREATE_FUNCS(srate)
+VCREATE_FUNCS(audiobitrate)
+VCREATE_FUNCS(audiocrc)
+VCREATE_FUNCS(audioemphasis)
+VCREATE_FUNCS(vbr)
+VCREATE_FUNCS(videobitrate)
+VCREATE_FUNCS(videopeak)
+VCREATE_FUNCS(input)
+VCREATE_FUNCS(audiomode)
+VCREATE_FUNCS(res_hor)
+VCREATE_FUNCS(res_ver)
+VCREATE_FUNCS(interlace)
+VCREATE_FUNCS(audiolayer)
+
+#define MIN_FREQ 55250000L
+#define MAX_FREQ 850000000L
+
+/* Table definition of all controls which can be manipulated */
+static const struct pvr2_ctl_info control_defs[] = {
+       {
+               .v4l_id = V4L2_CID_BRIGHTNESS,
+               .desc = "Brightness",
+               .name = "brightness",
+               .default_value = 128,
+               DEFREF(brightness),
+               DEFINT(0,255),
+       },{
+               .v4l_id = V4L2_CID_CONTRAST,
+               .desc = "Contrast",
+               .name = "contrast",
+               .default_value = 68,
+               DEFREF(contrast),
+               DEFINT(0,127),
+       },{
+               .v4l_id = V4L2_CID_SATURATION,
+               .desc = "Saturation",
+               .name = "saturation",
+               .default_value = 64,
+               DEFREF(saturation),
+               DEFINT(0,127),
+       },{
+               .v4l_id = V4L2_CID_HUE,
+               .desc = "Hue",
+               .name = "hue",
+               .default_value = 0,
+               DEFREF(hue),
+               DEFINT(-128,127),
+       },{
+               .v4l_id = V4L2_CID_AUDIO_VOLUME,
+               .desc = "Volume",
+               .name = "volume",
+               .default_value = 65535,
+               DEFREF(volume),
+               DEFINT(0,65535),
+       },{
+               .v4l_id = V4L2_CID_AUDIO_BALANCE,
+               .desc = "Balance",
+               .name = "balance",
+               .default_value = 0,
+               DEFREF(balance),
+               DEFINT(-32768,32767),
+       },{
+               .v4l_id = V4L2_CID_AUDIO_BASS,
+               .desc = "Bass",
+               .name = "bass",
+               .default_value = 0,
+               DEFREF(bass),
+               DEFINT(-32768,32767),
+       },{
+               .v4l_id = V4L2_CID_AUDIO_TREBLE,
+               .desc = "Treble",
+               .name = "treble",
+               .default_value = 0,
+               DEFREF(treble),
+               DEFINT(-32768,32767),
+       },{
+               .v4l_id = V4L2_CID_AUDIO_MUTE,
+               .desc = "Mute",
+               .name = "mute",
+               .default_value = 0,
+               DEFREF(mute),
+               DEFINT(0,1),
+       },{
+               .v4l_id = V4L2_CID_PVR_SRATE,
+               .desc = "Sample rate",
+               .name = "srate",
+               .default_value = PVR2_CVAL_SRATE_48,
+               DEFREF(srate),
+               DEFENUM(control_values_srate),
+       },{
+               .v4l_id = V4L2_CID_PVR_AUDIOBITRATE,
+               .desc = "Audio Bitrate",
+               .name = "audio_bitrate",
+               .default_value = PVR2_CVAL_AUDIOBITRATE_224,
+               DEFREF(audiobitrate),
+               DEFENUM(control_values_audiobitrate),
+       },{
+               .v4l_id = V4L2_CID_PVR_AUDIOCRC,
+               .desc = "Audio CRC",
+               .name = "audio_crc",
+               .default_value = 1,
+               DEFREF(audiocrc),
+               DEFINT(0,1),
+       },{
+               .desc = "Audio Layer",
+               .name = "audio_layer",
+               .default_value = 2,
+               DEFREF(audiolayer),
+               DEFINT(0,3),
+       },{
+               .v4l_id = V4L2_CID_PVR_AUDIOEMPHASIS,
+               .desc = "Audio Emphasis",
+               .name = "audio_emphasis",
+               .default_value = PVR2_CVAL_AUDIOEMPHASIS_NONE,
+               DEFREF(audioemphasis),
+               DEFENUM(control_values_audioemphasis),
+       },{
+               .desc = "Interlace mode",
+               .name = "interlace",
+               .internal_id = PVR2_CID_INTERLACE,
+               .default_value = 0,
+               DEFREF(interlace),
+               DEFINT(0,1),
+       },{
+               .v4l_id = V4L2_CID_PVR_VBR,
+               .desc = "Variable video bitrate",
+               .name = "vbr",
+               .default_value = 0,
+               DEFREF(vbr),
+               DEFINT(0,1),
+       },{
+               .v4l_id = V4L2_CID_PVR_VIDEOBITRATE,
+               .desc = "Average video bitrate",
+               .name = "video_average_bitrate",
+               .default_value = 6000000,
+               DEFREF(videobitrate),
+               DEFINT(500000,20000000),
+       },{
+               .v4l_id = V4L2_CID_PVR_VIDEOPEAK,
+               .desc = "Peak video bitrate",
+               .name = "video_peak_bitrate",
+               .default_value = 6000000,
+               DEFREF(videopeak),
+               DEFINT(500000,20000000),
+       },{
+               .desc = "Video Source",
+               .name = "input",
+               .internal_id = PVR2_CID_INPUT,
+               .default_value = PVR2_CVAL_INPUT_TV,
+               DEFREF(input),
+               DEFENUM(control_values_input),
+       },{
+               .desc = "Audio Mode",
+               .name = "audio_mode",
+               .internal_id = PVR2_CID_AUDIOMODE,
+               .default_value = V4L2_TUNER_MODE_STEREO,
+               DEFREF(audiomode),
+               DEFENUM(control_values_audiomode),
+       },{
+               .desc = "Tuner Frequency (Hz)",
+               .name = "frequency",
+               .internal_id = PVR2_CID_FREQUENCY,
+               .default_value = 175250000L,
+               .set_value = ctrl_freq_set,
+               .get_value = ctrl_freq_get,
+               .is_dirty = ctrl_freq_is_dirty,
+               .clear_dirty = ctrl_freq_clear_dirty,
+               DEFINT(MIN_FREQ,MAX_FREQ),
+       },{
+               .desc = "Channel",
+               .name = "channel",
+               .set_value = ctrl_channel_set,
+               .get_value = ctrl_channel_get,
+               DEFINT(0,FREQTABLE_SIZE),
+       },{
+               .desc = "Channel Program Frequency",
+               .name = "freq_table_value",
+               .set_value = ctrl_channelfreq_set,
+               .get_value = ctrl_channelfreq_get,
+               DEFINT(MIN_FREQ,MAX_FREQ),
+       },{
+               .desc = "Channel Program ID",
+               .name = "freq_table_channel",
+               .set_value = ctrl_channelprog_set,
+               .get_value = ctrl_channelprog_get,
+               DEFINT(0,FREQTABLE_SIZE),
+       },{
+               .desc = "Horizontal capture resolution",
+               .name = "resolution_hor",
+               .internal_id = PVR2_CID_HRES,
+               .default_value = 720,
+               DEFREF(res_hor),
+               DEFINT(320,720),
+       },{
+               .desc = "Vertical capture resolution",
+               .name = "resolution_ver",
+               .internal_id = PVR2_CID_VRES,
+               .default_value = 480,
+               DEFREF(res_ver),
+               DEFINT(200,625),
+       },{
+               .desc = "Streaming Enabled",
+               .name = "streaming_enabled",
+               .get_value = ctrl_streamingenabled_get,
+               DEFINT(0,1),
+       },{
+               .desc = "USB Speed",
+               .name = "usb_speed",
+               .get_value = ctrl_hsm_get,
+               DEFENUM(control_values_hsm),
+       },{
+               .desc = "Signal Present",
+               .name = "signal_present",
+               .get_value = ctrl_signal_get,
+               DEFINT(0,1),
+       },{
+               .desc = "Video Standards Available Mask",
+               .name = "video_standard_mask_available",
+               .internal_id = PVR2_CID_STDAVAIL,
+               .skip_init = !0,
+               .get_value = ctrl_stdavail_get,
+               .set_value = ctrl_stdavail_set,
+               .val_to_sym = ctrl_std_val_to_sym,
+               .sym_to_val = ctrl_std_sym_to_val,
+               .type = pvr2_ctl_bitmask,
+       },{
+               .desc = "Video Standards In Use Mask",
+               .name = "video_standard_mask_active",
+               .internal_id = PVR2_CID_STDCUR,
+               .skip_init = !0,
+               .get_value = ctrl_stdcur_get,
+               .set_value = ctrl_stdcur_set,
+               .is_dirty = ctrl_stdcur_is_dirty,
+               .clear_dirty = ctrl_stdcur_clear_dirty,
+               .val_to_sym = ctrl_std_val_to_sym,
+               .sym_to_val = ctrl_std_sym_to_val,
+               .type = pvr2_ctl_bitmask,
+       },{
+               .desc = "Subsystem enabled mask",
+               .name = "debug_subsys_mask",
+               .skip_init = !0,
+               .get_value = ctrl_subsys_get,
+               .set_value = ctrl_subsys_set,
+               DEFMASK(PVR2_SUBSYS_ALL,control_values_subsystem),
+       },{
+               .desc = "Subsystem stream mask",
+               .name = "debug_subsys_stream_mask",
+               .skip_init = !0,
+               .get_value = ctrl_subsys_stream_get,
+               .set_value = ctrl_subsys_stream_set,
+               DEFMASK(PVR2_SUBSYS_ALL,control_values_subsystem),
+       },{
+               .desc = "Video Standard Name",
+               .name = "video_standard",
+               .internal_id = PVR2_CID_STDENUM,
+               .skip_init = !0,
+               .get_value = ctrl_stdenumcur_get,
+               .set_value = ctrl_stdenumcur_set,
+               .is_dirty = ctrl_stdenumcur_is_dirty,
+               .clear_dirty = ctrl_stdenumcur_clear_dirty,
+               .type = pvr2_ctl_enum,
+       }
+};
+
+#define CTRL_COUNT (sizeof(control_defs)/sizeof(control_defs[0]))
+
+
+const char *pvr2_config_get_name(enum pvr2_config cfg)
+{
+       switch (cfg) {
+       case pvr2_config_empty: return "empty";
+       case pvr2_config_mpeg: return "mpeg";
+       case pvr2_config_vbi: return "vbi";
+       case pvr2_config_radio: return "radio";
+       }
+       return "<unknown>";
+}
+
+
+struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *hdw)
+{
+       return hdw->usb_dev;
+}
+
+
+unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *hdw)
+{
+       return hdw->serial_number;
+}
+
+
+struct pvr2_hdw *pvr2_hdw_find(int unit_number)
+{
+       if (unit_number < 0) return 0;
+       if (unit_number >= PVR_NUM) return 0;
+       return unit_pointers[unit_number];
+}
+
+
+int pvr2_hdw_get_unit_number(struct pvr2_hdw *hdw)
+{
+       return hdw->unit_number;
+}
+
+
+/* Attempt to locate one of the given set of files.  Messages are logged
+   appropriate to what has been found.  The return value will be 0 or
+   greater on success (it will be the index of the file name found) and
+   fw_entry will be filled in.  Otherwise a negative error is returned on
+   failure.  If the return value is -ENOENT then no viable firmware file
+   could be located. */
+static int pvr2_locate_firmware(struct pvr2_hdw *hdw,
+                               const struct firmware **fw_entry,
+                               const char *fwtypename,
+                               unsigned int fwcount,
+                               const char *fwnames[])
+{
+       unsigned int idx;
+       int ret = -EINVAL;
+       for (idx = 0; idx < fwcount; idx++) {
+               ret = request_firmware(fw_entry,
+                                      fwnames[idx],
+                                      &hdw->usb_dev->dev);
+               if (!ret) {
+                       trace_firmware("Located %s firmware: %s;"
+                                      " uploading...",
+                                      fwtypename,
+                                      fwnames[idx]);
+                       return idx;
+               }
+               if (ret == -ENOENT) continue;
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "request_firmware fatal error with code=%d",ret);
+               return ret;
+       }
+       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                  "***WARNING***"
+                  " Device %s firmware"
+                  " seems to be missing.",
+                  fwtypename);
+       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                  "Did you install the pvrusb2 firmware files"
+                  " in their proper location?");
+       if (fwcount == 1) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "request_firmware unable to locate %s file %s",
+                          fwtypename,fwnames[0]);
+       } else {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "request_firmware unable to locate"
+                          " one of the following %s files:",
+                          fwtypename);
+               for (idx = 0; idx < fwcount; idx++) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "request_firmware: Failed to find %s",
+                                  fwnames[idx]);
+               }
+       }
+       return ret;
+}
+
+
+/*
+ * pvr2_upload_firmware1().
+ *
+ * Send the 8051 firmware to the device.  After the upload, arrange for
+ * device to re-enumerate.
+ *
+ * NOTE : the pointer to the firmware data given by request_firmware()
+ * is not suitable for an usb transaction.
+ *
+ */
+int pvr2_upload_firmware1(struct pvr2_hdw *hdw)
+{
+       const struct firmware *fw_entry = 0;
+       void  *fw_ptr;
+       unsigned int pipe;
+       int ret;
+       u16 address;
+       static const char *fw_files_29xxx[] = {
+               "v4l-pvrusb2-29xxx-01.fw",
+       };
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+       static const char *fw_files_24xxx[] = {
+               "v4l-pvrusb2-24xxx-01.fw",
+       };
+#endif
+       static const struct pvr2_string_table fw_file_defs[] = {
+               [PVR2_HDW_TYPE_29XXX] = {
+                       fw_files_29xxx,
+                       sizeof(fw_files_29xxx)/sizeof(fw_files_29xxx[0]),
+               },
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+               [PVR2_HDW_TYPE_24XXX] = {
+                       fw_files_24xxx,
+                       sizeof(fw_files_24xxx)/sizeof(fw_files_24xxx[0]),
+               },
+#endif
+       };
+       hdw->fw1_state = FW1_STATE_FAILED; // default result
+
+       trace_firmware("pvr2_upload_firmware1");
+
+       ret = pvr2_locate_firmware(hdw,&fw_entry,"fx2 controller",
+                                  fw_file_defs[hdw->hdw_type].cnt,
+                                  fw_file_defs[hdw->hdw_type].lst);
+       if (ret < 0) {
+               if (ret == -ENOENT) hdw->fw1_state = FW1_STATE_MISSING;
+               return ret;
+       }
+
+       usb_settoggle(hdw->usb_dev, 0 & 0xf, !(0 & USB_DIR_IN), 0);
+       usb_clear_halt(hdw->usb_dev, usb_sndbulkpipe(hdw->usb_dev, 0 & 0x7f));
+
+       pipe = usb_sndctrlpipe(hdw->usb_dev, 0);
+
+       if (fw_entry->size != 0x2000){
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,"wrong fx2 firmware size");
+               release_firmware(fw_entry);
+               return -ENOMEM;
+       }
+
+       fw_ptr = kmalloc(0x800, GFP_KERNEL);
+       if (fw_ptr == NULL){
+               release_firmware(fw_entry);
+               return -ENOMEM;
+       }
+
+       /* We have to hold the CPU during firmware upload. */
+       pvr2_hdw_cpureset_assert(hdw,1);
+
+       /* upload the firmware to address 0000-1fff in 2048 (=0x800) bytes
+          chunk. */
+
+       ret = 0;
+       for(address = 0; address < fw_entry->size; address += 0x800) {
+               memcpy(fw_ptr, fw_entry->data + address, 0x800);
+               ret += usb_control_msg(hdw->usb_dev, pipe, 0xa0, 0x40, address,
+                                      0, fw_ptr, 0x800, HZ);
+       }
+
+       trace_firmware("Upload done, releasing device's CPU");
+
+       /* Now release the CPU.  It will disconnect and reconnect later. */
+       pvr2_hdw_cpureset_assert(hdw,0);
+
+       kfree(fw_ptr);
+       release_firmware(fw_entry);
+
+       trace_firmware("Upload done (%d bytes sent)",ret);
+
+       /* We should have written 8192 bytes */
+       if (ret == 8192) {
+               hdw->fw1_state = FW1_STATE_RELOAD;
+               return 0;
+       }
+
+       return -EIO;
+}
+
+
+/*
+ * pvr2_upload_firmware2()
+ *
+ * This uploads encoder firmware on endpoint 2.
+ *
+ */
+
+int pvr2_upload_firmware2(struct pvr2_hdw *hdw)
+{
+       const struct firmware *fw_entry = 0;
+       void  *fw_ptr;
+       unsigned int pipe, fw_len, fw_done;
+       int actual_length;
+       int ret = 0;
+       int fwidx;
+       static const char *fw_files[] = {
+               CX2341X_FIRM_ENC_FILENAME,
+       };
+
+       trace_firmware("pvr2_upload_firmware2");
+
+       ret = pvr2_locate_firmware(hdw,&fw_entry,"encoder",
+                                  sizeof(fw_files)/sizeof(fw_files[0]),
+                                  fw_files);
+       if (ret < 0) return ret;
+       fwidx = ret;
+       ret = 0;
+
+       /* First prepare firmware loading */
+       ret |= pvr2_write_register(hdw, 0x0048, 0xffffffff); /*interrupt mask*/
+       ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000088); /*gpio dir*/
+       ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/
+       ret |= pvr2_hdw_cmd_deep_reset(hdw);
+       ret |= pvr2_write_register(hdw, 0xa064, 0x00000000); /*APU command*/
+       ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000408); /*gpio dir*/
+       ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/
+       ret |= pvr2_write_register(hdw, 0x9058, 0xffffffed); /*VPU ctrl*/
+       ret |= pvr2_write_register(hdw, 0x9054, 0xfffffffd); /*reset hw blocks*/
+       ret |= pvr2_write_register(hdw, 0x07f8, 0x80000800); /*encoder SDRAM refresh*/
+       ret |= pvr2_write_register(hdw, 0x07fc, 0x0000001a); /*encoder SDRAM pre-charge*/
+       ret |= pvr2_write_register(hdw, 0x0700, 0x00000000); /*I2C clock*/
+       ret |= pvr2_write_register(hdw, 0xaa00, 0x00000000); /*unknown*/
+       ret |= pvr2_write_register(hdw, 0xaa04, 0x00057810); /*unknown*/
+       ret |= pvr2_write_register(hdw, 0xaa10, 0x00148500); /*unknown*/
+       ret |= pvr2_write_register(hdw, 0xaa18, 0x00840000); /*unknown*/
+       ret |= pvr2_write_u8(hdw, 0x52, 0);
+       ret |= pvr2_write_u16(hdw, 0x0600, 0);
+
+       if (ret) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "firmware2 upload prep failed, ret=%d",ret);
+               release_firmware(fw_entry);
+               return ret;
+       }
+
+       /* Now send firmware */
+
+       fw_len = fw_entry->size;
+
+       if (fw_len % FIRMWARE_CHUNK_SIZE) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "size of %s firmware"
+                          " must be a multiple of 8192B",
+                          fw_files[fwidx]);
+               release_firmware(fw_entry);
+               return -1;
+       }
+
+       fw_ptr = kmalloc(FIRMWARE_CHUNK_SIZE, GFP_KERNEL);
+       if (fw_ptr == NULL){
+               release_firmware(fw_entry);
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "failed to allocate memory for firmware2 upload");
+               return -ENOMEM;
+       }
+
+       pipe = usb_sndbulkpipe(hdw->usb_dev, PVR2_FIRMWARE_ENDPOINT);
+
+       for (fw_done = 0 ; (fw_done < fw_len) && !ret ;
+            fw_done += FIRMWARE_CHUNK_SIZE ) {
+               int i;
+               memcpy(fw_ptr, fw_entry->data + fw_done, FIRMWARE_CHUNK_SIZE);
+               /* Usbsnoop log  shows that we must swap bytes... */
+               for (i = 0; i < FIRMWARE_CHUNK_SIZE/4 ; i++)
+                       ((u32 *)fw_ptr)[i] = ___swab32(((u32 *)fw_ptr)[i]);
+
+               ret |= usb_bulk_msg(hdw->usb_dev, pipe, fw_ptr,
+                                   FIRMWARE_CHUNK_SIZE,
+                                   &actual_length, HZ);
+               ret |= (actual_length != FIRMWARE_CHUNK_SIZE);
+       }
+
+       trace_firmware("upload of %s : %i / %i ",
+                      fw_files[fwidx],fw_done,fw_len);
+
+       kfree(fw_ptr);
+       release_firmware(fw_entry);
+
+       if (ret) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "firmware2 upload transfer failure");
+               return ret;
+       }
+
+       /* Finish upload */
+
+       ret |= pvr2_write_register(hdw, 0x9054, 0xffffffff); /*reset hw blocks*/
+       ret |= pvr2_write_register(hdw, 0x9058, 0xffffffe8); /*VPU ctrl*/
+       ret |= pvr2_write_u16(hdw, 0x0600, 0);
+
+       if (ret) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "firmware2 upload post-proc failure");
+       } else {
+               hdw->subsys_enabled_mask |= (1<<PVR2_SUBSYS_B_ENC_FIRMWARE);
+       }
+       return ret;
+}
+
+
+#define FIRMWARE_RECOVERY_BITS \
+       ((1<<PVR2_SUBSYS_B_ENC_CFG) | \
+        (1<<PVR2_SUBSYS_B_ENC_RUN) | \
+        (1<<PVR2_SUBSYS_B_ENC_FIRMWARE) | \
+        (1<<PVR2_SUBSYS_B_USBSTREAM_RUN))
+
+/*
+
+  This single function is key to pretty much everything.  The pvrusb2
+  device can logically be viewed as a series of subsystems which can be
+  stopped / started or unconfigured / configured.  To get things streaming,
+  one must configure everything and start everything, but there may be
+  various reasons over time to deconfigure something or stop something.
+  This function handles all of this activity.  Everything EVERYWHERE that
+  must affect a subsystem eventually comes here to do the work.
+
+  The current state of all subsystems is represented by a single bit mask,
+  known as subsys_enabled_mask.  The bit positions are defined by the
+  PVR2_SUBSYS_xxxx macros, with one subsystem per bit position.  At any
+  time the set of configured or active subsystems can be queried just by
+  looking at that mask.  To change bits in that mask, this function here
+  must be called.  The "msk" argument indicates which bit positions to
+  change, and the "val" argument defines the new values for the positions
+  defined by "msk".
+
+  There is a priority ordering of starting / stopping things, and for
+  multiple requested changes, this function implements that ordering.
+  (Thus we will act on a request to load encoder firmware before we
+  configure the encoder.)  In addition to priority ordering, there is a
+  recovery strategy implemented here.  If a particular step fails and we
+  detect that failure, this function will clear the affected subsystem bits
+  and restart.  Thus we have a means for recovering from a dead encoder:
+  Clear all bits that correspond to subsystems that we need to restart /
+  reconfigure and start over.
+
+*/
+void pvr2_hdw_subsys_bit_chg_no_lock(struct pvr2_hdw *hdw,
+                                    unsigned long msk,unsigned long val)
+{
+       unsigned long nmsk;
+       unsigned long vmsk;
+       int ret;
+       unsigned int tryCount = 0;
+
+       if (!hdw->flag_ok) return;
+
+       msk &= PVR2_SUBSYS_ALL;
+
+       for (;;) {
+               tryCount++;
+               vmsk = hdw->subsys_enabled_mask & PVR2_SUBSYS_ALL;
+               nmsk = (vmsk & ~msk) | (val & msk);
+               if (!(nmsk ^ vmsk)) break;
+               if (tryCount > 4) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "Too many retries when configuring device;"
+                                  " giving up");
+                       pvr2_hdw_render_useless(hdw);
+                       break;
+               }
+               if (tryCount > 1) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "Retrying device reconfiguration");
+               }
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "subsys mask changing 0x%lx:0x%lx"
+                          " from 0x%lx to 0x%lx",
+                          msk,val,hdw->subsys_enabled_mask,nmsk);
+
+               vmsk = (nmsk ^ hdw->subsys_enabled_mask) &
+                       hdw->subsys_enabled_mask;
+               if (vmsk) {
+                       if (vmsk & (1<<PVR2_SUBSYS_B_ENC_RUN)) {
+                               pvr2_trace(PVR2_TRACE_CTL,
+                                          "/*---TRACE_CTL----*/"
+                                          " pvr2_encoder_stop");
+                               ret = pvr2_encoder_stop(hdw);
+                               if (ret) {
+                                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                                  "Error recovery initiated");
+                                       hdw->subsys_enabled_mask &=
+                                               ~FIRMWARE_RECOVERY_BITS;
+                                       continue;
+                               }
+                       }
+                       if (vmsk & (1<<PVR2_SUBSYS_B_USBSTREAM_RUN)) {
+                               pvr2_trace(PVR2_TRACE_CTL,
+                                          "/*---TRACE_CTL----*/"
+                                          " pvr2_hdw_cmd_usbstream(0)");
+                               pvr2_hdw_cmd_usbstream(hdw,0);
+                       }
+                       if (vmsk & (1<<PVR2_SUBSYS_B_DIGITIZER_RUN)) {
+                               pvr2_trace(PVR2_TRACE_CTL,
+                                          "/*---TRACE_CTL----*/"
+                                          " decoder disable");
+                               if (hdw->decoder_ctrl) {
+                                       hdw->decoder_ctrl->enable(
+                                               hdw->decoder_ctrl->ctxt,0);
+                               } else {
+                                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                                  "WARNING:"
+                                                  " No decoder present");
+                               }
+                               hdw->subsys_enabled_mask &=
+                                       ~(1<<PVR2_SUBSYS_B_DIGITIZER_RUN);
+                       }
+                       if (vmsk & PVR2_SUBSYS_CFG_ALL) {
+                               hdw->subsys_enabled_mask &=
+                                       ~(vmsk & PVR2_SUBSYS_CFG_ALL);
+                       }
+               }
+               vmsk = (nmsk ^ hdw->subsys_enabled_mask) & nmsk;
+               if (vmsk) {
+                       if (vmsk & (1<<PVR2_SUBSYS_B_ENC_FIRMWARE)) {
+                               pvr2_trace(PVR2_TRACE_CTL,
+                                          "/*---TRACE_CTL----*/"
+                                          " pvr2_upload_firmware2");
+                               ret = pvr2_upload_firmware2(hdw);
+                               if (ret) {
+                                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                                  "Failure uploading encoder"
+                                                  " firmware");
+                                       pvr2_hdw_render_useless(hdw);
+                                       break;
+                               }
+                       }
+                       if (vmsk & (1<<PVR2_SUBSYS_B_ENC_CFG)) {
+                               pvr2_trace(PVR2_TRACE_CTL,
+                                          "/*---TRACE_CTL----*/"
+                                          " pvr2_encoder_configure");
+                               ret = pvr2_encoder_configure(hdw);
+                               if (ret) {
+                                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                                  "Error recovery initiated");
+                                       hdw->subsys_enabled_mask &=
+                                               ~FIRMWARE_RECOVERY_BITS;
+                                       continue;
+                               }
+                       }
+                       if (vmsk & (1<<PVR2_SUBSYS_B_DIGITIZER_RUN)) {
+                               pvr2_trace(PVR2_TRACE_CTL,
+                                          "/*---TRACE_CTL----*/"
+                                          " decoder enable");
+                               if (hdw->decoder_ctrl) {
+                                       hdw->decoder_ctrl->enable(
+                                               hdw->decoder_ctrl->ctxt,!0);
+                               } else {
+                                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                                  "WARNING:"
+                                                  " No decoder present");
+                               }
+                               hdw->subsys_enabled_mask |=
+                                       (1<<PVR2_SUBSYS_B_DIGITIZER_RUN);
+                       }
+                       if (vmsk & (1<<PVR2_SUBSYS_B_USBSTREAM_RUN)) {
+                               pvr2_trace(PVR2_TRACE_CTL,
+                                          "/*---TRACE_CTL----*/"
+                                          " pvr2_hdw_cmd_usbstream(1)");
+                               pvr2_hdw_cmd_usbstream(hdw,!0);
+                       }
+                       if (vmsk & (1<<PVR2_SUBSYS_B_ENC_RUN)) {
+                               pvr2_trace(PVR2_TRACE_CTL,
+                                          "/*---TRACE_CTL----*/"
+                                          " pvr2_encoder_start");
+                               ret = pvr2_encoder_start(hdw);
+                               if (ret) {
+                                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                                  "Error recovery initiated");
+                                       hdw->subsys_enabled_mask &=
+                                               ~FIRMWARE_RECOVERY_BITS;
+                                       continue;
+                               }
+                       }
+               }
+       }
+}
+
+
+void pvr2_hdw_subsys_bit_chg(struct pvr2_hdw *hdw,
+                            unsigned long msk,unsigned long val)
+{
+       LOCK_TAKE(hdw->big_lock); do {
+               pvr2_hdw_subsys_bit_chg_no_lock(hdw,msk,val);
+       } while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+void pvr2_hdw_subsys_bit_set(struct pvr2_hdw *hdw,unsigned long msk)
+{
+       pvr2_hdw_subsys_bit_chg(hdw,msk,msk);
+}
+
+
+void pvr2_hdw_subsys_bit_clr(struct pvr2_hdw *hdw,unsigned long msk)
+{
+       pvr2_hdw_subsys_bit_chg(hdw,msk,0);
+}
+
+
+unsigned long pvr2_hdw_subsys_get(struct pvr2_hdw *hdw)
+{
+       return hdw->subsys_enabled_mask;
+}
+
+
+unsigned long pvr2_hdw_subsys_stream_get(struct pvr2_hdw *hdw)
+{
+       return hdw->subsys_stream_mask;
+}
+
+
+void pvr2_hdw_subsys_stream_bit_chg_no_lock(struct pvr2_hdw *hdw,
+                                           unsigned long msk,
+                                           unsigned long val)
+{
+       unsigned long val2;
+       msk &= PVR2_SUBSYS_ALL;
+       val2 = ((hdw->subsys_stream_mask & ~msk) | (val & msk));
+       pvr2_trace(PVR2_TRACE_INIT,
+                  "stream mask changing 0x%lx:0x%lx from 0x%lx to 0x%lx",
+                  msk,val,hdw->subsys_stream_mask,val2);
+       hdw->subsys_stream_mask = val2;
+}
+
+
+void pvr2_hdw_subsys_stream_bit_chg(struct pvr2_hdw *hdw,
+                                   unsigned long msk,
+                                   unsigned long val)
+{
+       LOCK_TAKE(hdw->big_lock); do {
+               pvr2_hdw_subsys_stream_bit_chg_no_lock(hdw,msk,val);
+       } while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+int pvr2_hdw_set_streaming_no_lock(struct pvr2_hdw *hdw,int enableFl)
+{
+       if ((!enableFl) == !(hdw->flag_streaming_enabled)) return 0;
+       if (enableFl) {
+               pvr2_trace(PVR2_TRACE_START_STOP,
+                          "/*--TRACE_STREAM--*/ enable");
+               pvr2_hdw_subsys_bit_chg_no_lock(hdw,~0,~0);
+       } else {
+               pvr2_trace(PVR2_TRACE_START_STOP,
+                          "/*--TRACE_STREAM--*/ disable");
+               pvr2_hdw_subsys_bit_chg_no_lock(hdw,hdw->subsys_stream_mask,0);
+       }
+       if (!hdw->flag_ok) return -EIO;
+       hdw->flag_streaming_enabled = enableFl != 0;
+       return 0;
+}
+
+
+int pvr2_hdw_get_streaming(struct pvr2_hdw *hdw)
+{
+       return hdw->flag_streaming_enabled != 0;
+}
+
+
+int pvr2_hdw_set_streaming(struct pvr2_hdw *hdw,int enable_flag)
+{
+       int ret;
+       LOCK_TAKE(hdw->big_lock); do {
+               ret = pvr2_hdw_set_streaming_no_lock(hdw,enable_flag);
+       } while (0); LOCK_GIVE(hdw->big_lock);
+       return ret;
+}
+
+
+int pvr2_hdw_set_stream_type_no_lock(struct pvr2_hdw *hdw,
+                                    enum pvr2_config config)
+{
+       unsigned long sm = hdw->subsys_enabled_mask;
+       if (!hdw->flag_ok) return -EIO;
+       pvr2_hdw_subsys_bit_chg_no_lock(hdw,hdw->subsys_stream_mask,0);
+       hdw->config = config;
+       pvr2_hdw_subsys_bit_chg_no_lock(hdw,~0,sm);
+       return 0;
+}
+
+
+int pvr2_hdw_set_stream_type(struct pvr2_hdw *hdw,enum pvr2_config config)
+{
+       int ret;
+       if (!hdw->flag_ok) return -EIO;
+       LOCK_TAKE(hdw->big_lock);
+       ret = pvr2_hdw_set_stream_type_no_lock(hdw,config);
+       LOCK_GIVE(hdw->big_lock);
+       return ret;
+}
+
+
+static int get_default_tuner_type(struct pvr2_hdw *hdw)
+{
+       int unit_number = hdw->unit_number;
+       int tp = -1;
+       if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+               tp = tuner[unit_number];
+       }
+       if (tp < 0) return -EINVAL;
+       hdw->tuner_type = tp;
+       return 0;
+}
+
+
+static v4l2_std_id get_default_standard(struct pvr2_hdw *hdw)
+{
+       int unit_number = hdw->unit_number;
+       int tp = 0;
+       if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+               tp = video_std[unit_number];
+       }
+       return tp;
+}
+
+
+static unsigned int get_default_error_tolerance(struct pvr2_hdw *hdw)
+{
+       int unit_number = hdw->unit_number;
+       int tp = 0;
+       if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+               tp = tolerance[unit_number];
+       }
+       return tp;
+}
+
+
+static int pvr2_hdw_check_firmware(struct pvr2_hdw *hdw)
+{
+       /* Try a harmless request to fetch the eeprom's address over
+          endpoint 1.  See what happens.  Only the full FX2 image can
+          respond to this.  If this probe fails then likely the FX2
+          firmware needs be loaded. */
+       int result;
+       LOCK_TAKE(hdw->ctl_lock); do {
+               hdw->cmd_buffer[0] = 0xeb;
+               result = pvr2_send_request_ex(hdw,HZ*1,!0,
+                                          hdw->cmd_buffer,1,
+                                          hdw->cmd_buffer,1);
+               if (result < 0) break;
+       } while(0); LOCK_GIVE(hdw->ctl_lock);
+       if (result) {
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "Probe of device endpoint 1 result status %d",
+                          result);
+       } else {
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "Probe of device endpoint 1 succeeded");
+       }
+       return result == 0;
+}
+
+static void pvr2_hdw_setup_std(struct pvr2_hdw *hdw)
+{
+       char buf[40];
+       unsigned int bcnt;
+       v4l2_std_id std1,std2;
+
+       std1 = get_default_standard(hdw);
+
+       bcnt = pvr2_std_id_to_str(buf,sizeof(buf),hdw->std_mask_eeprom);
+       pvr2_trace(PVR2_TRACE_INIT,
+                  "Supported video standard(s) reported by eeprom: %.*s",
+                  bcnt,buf);
+
+       hdw->std_mask_avail = hdw->std_mask_eeprom;
+
+       std2 = std1 & ~hdw->std_mask_avail;
+       if (std2) {
+               bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std2);
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "Expanding supported video standards"
+                          " to include: %.*s",
+                          bcnt,buf);
+               hdw->std_mask_avail |= std2;
+       }
+
+       pvr2_hdw_internal_set_std_avail(hdw);
+
+       if (std1) {
+               bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std1);
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "Initial video standard forced to %.*s",
+                          bcnt,buf);
+               hdw->std_mask_cur = std1;
+               hdw->std_dirty = !0;
+               pvr2_hdw_internal_find_stdenum(hdw);
+               return;
+       }
+
+       if (hdw->std_enum_cnt > 1) {
+               // Autoselect the first listed standard
+               hdw->std_enum_cur = 1;
+               hdw->std_mask_cur = hdw->std_defs[hdw->std_enum_cur-1].id;
+               hdw->std_dirty = !0;
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "Initial video standard auto-selected to %s",
+                          hdw->std_defs[hdw->std_enum_cur-1].name);
+               return;
+       }
+
+       pvr2_trace(PVR2_TRACE_EEPROM,
+                  "Unable to select a viable initial video standard");
+}
+
+
+static void pvr2_hdw_setup_low(struct pvr2_hdw *hdw)
+{
+       int ret;
+       unsigned int idx;
+       struct pvr2_ctrl *cptr;
+       int reloadFl = 0;
+       if (!reloadFl) {
+               reloadFl = (hdw->usb_intf->cur_altsetting->desc.bNumEndpoints
+                           == 0);
+               if (reloadFl) {
+                       pvr2_trace(PVR2_TRACE_INIT,
+                                  "USB endpoint config looks strange"
+                                  "; possibly firmware needs to be loaded");
+               }
+       }
+       if (!reloadFl) {
+               reloadFl = !pvr2_hdw_check_firmware(hdw);
+               if (reloadFl) {
+                       pvr2_trace(PVR2_TRACE_INIT,
+                                  "Check for FX2 firmware failed"
+                                  "; possibly firmware needs to be loaded");
+               }
+       }
+       if (reloadFl) {
+               if (pvr2_upload_firmware1(hdw) != 0) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "Failure uploading firmware1");
+               }
+               return;
+       }
+       hdw->fw1_state = FW1_STATE_OK;
+
+       if (initusbreset) {
+               pvr2_hdw_device_reset(hdw);
+       }
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+
+       for (idx = 0; idx < pvr2_client_lists[hdw->hdw_type].cnt; idx++) {
+               request_module(pvr2_client_lists[hdw->hdw_type].lst[idx]);
+       }
+
+       pvr2_hdw_cmd_powerup(hdw);
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+
+       if (pvr2_upload_firmware2(hdw)){
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,"device unstable!!");
+               pvr2_hdw_render_useless(hdw);
+               return;
+       }
+
+       // This step MUST happen after the earlier powerup step.
+       pvr2_i2c_core_init(hdw);
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+
+       for (idx = 0; idx < CTRL_COUNT; idx++) {
+               cptr = hdw->controls + idx;
+               if (cptr->info->skip_init) continue;
+               if (!cptr->info->set_value) continue;
+               cptr->info->set_value(cptr,~0,cptr->info->default_value);
+       }
+
+       // Do not use pvr2_reset_ctl_endpoints() here.  It is not
+       // thread-safe against the normal pvr2_send_request() mechanism.
+       // (We should make it thread safe).
+
+       ret = pvr2_hdw_get_eeprom_addr(hdw);
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+       if (ret < 0) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Unable to determine location of eeprom, skipping");
+       } else {
+               hdw->eeprom_addr = ret;
+               pvr2_eeprom_analyze(hdw);
+               if (!pvr2_hdw_dev_ok(hdw)) return;
+       }
+
+       pvr2_hdw_setup_std(hdw);
+
+       if (!get_default_tuner_type(hdw)) {
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "pvr2_hdw_setup: Tuner type overridden to %d",
+                          hdw->tuner_type);
+       }
+
+       hdw->tuner_updated = !0;
+       pvr2_i2c_core_check_stale(hdw);
+       hdw->tuner_updated = 0;
+
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+
+       pvr2_hdw_commit_ctl_internal(hdw);
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+
+       hdw->vid_stream = pvr2_stream_create();
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+       pvr2_trace(PVR2_TRACE_INIT,
+                  "pvr2_hdw_setup: video stream is %p",hdw->vid_stream);
+       if (hdw->vid_stream) {
+               idx = get_default_error_tolerance(hdw);
+               if (idx) {
+                       pvr2_trace(PVR2_TRACE_INIT,
+                                  "pvr2_hdw_setup: video stream %p"
+                                  " setting tolerance %u",
+                                  hdw->vid_stream,idx);
+               }
+               pvr2_stream_setup(hdw->vid_stream,hdw->usb_dev,
+                                 PVR2_VID_ENDPOINT,idx);
+       }
+
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+
+       /* Make sure everything is up to date */
+       pvr2_i2c_core_sync(hdw);
+
+       if (!pvr2_hdw_dev_ok(hdw)) return;
+
+       hdw->flag_init_ok = !0;
+}
+
+
+int pvr2_hdw_setup(struct pvr2_hdw *hdw)
+{
+       pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) begin",hdw);
+       LOCK_TAKE(hdw->big_lock); do {
+               pvr2_hdw_setup_low(hdw);
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "pvr2_hdw_setup(hdw=%p) done, ok=%d init_ok=%d",
+                          hdw,hdw->flag_ok,hdw->flag_init_ok);
+               if (pvr2_hdw_dev_ok(hdw)) {
+                       if (pvr2_hdw_init_ok(hdw)) {
+                               pvr2_trace(
+                                       PVR2_TRACE_INFO,
+                                       "Device initialization"
+                                       " completed successfully.");
+                               break;
+                       }
+                       if (hdw->fw1_state == FW1_STATE_RELOAD) {
+                               pvr2_trace(
+                                       PVR2_TRACE_INFO,
+                                       "Device microcontroller firmware"
+                                       " (re)loaded; it should now reset"
+                                       " and reconnect.");
+                               break;
+                       }
+                       pvr2_trace(
+                               PVR2_TRACE_ERROR_LEGS,
+                               "Device initialization was not successful.");
+                       if (hdw->fw1_state == FW1_STATE_MISSING) {
+                               pvr2_trace(
+                                       PVR2_TRACE_ERROR_LEGS,
+                                       "Giving up since device"
+                                       " microcontroller firmware"
+                                       " appears to be missing.");
+                               break;
+                       }
+               }
+               if (procreload) {
+                       pvr2_trace(
+                               PVR2_TRACE_ERROR_LEGS,
+                               "Attempting pvrusb2 recovery by reloading"
+                               " primary firmware.");
+                       pvr2_trace(
+                               PVR2_TRACE_ERROR_LEGS,
+                               "If this works, device should disconnect"
+                               " and reconnect in a sane state.");
+                       hdw->fw1_state = FW1_STATE_UNKNOWN;
+                       pvr2_upload_firmware1(hdw);
+               } else {
+                       pvr2_trace(
+                               PVR2_TRACE_ERROR_LEGS,
+                               "***WARNING*** pvrusb2 device hardware"
+                               " appears to be jammed"
+                               " and I can't clear it.");
+                       pvr2_trace(
+                               PVR2_TRACE_ERROR_LEGS,
+                               "You might need to power cycle"
+                               " the pvrusb2 device"
+                               " in order to recover.");
+               }
+       } while (0); LOCK_GIVE(hdw->big_lock);
+       pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) end",hdw);
+       return hdw->flag_init_ok;
+}
+
+
+/* Create and return a structure for interacting with the underlying
+   hardware */
+struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf,
+                                const struct usb_device_id *devid)
+{
+       unsigned int idx,cnt1,cnt2;
+       struct pvr2_hdw *hdw;
+       unsigned int hdw_type;
+       int valid_std_mask;
+       struct pvr2_ctrl *cptr;
+       __u8 ifnum;
+
+       hdw_type = devid - pvr2_device_table;
+       if (hdw_type >=
+           sizeof(pvr2_device_names)/sizeof(pvr2_device_names[0])) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Bogus device type of %u reported",hdw_type);
+               return 0;
+       }
+
+       hdw = kmalloc(sizeof(*hdw),GFP_KERNEL);
+       pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_create: hdw=%p, type \"%s\"",
+                  hdw,pvr2_device_names[hdw_type]);
+       if (!hdw) goto fail;
+       memset(hdw,0,sizeof(*hdw));
+
+       hdw->controls = kmalloc(sizeof(struct pvr2_ctrl) * CTRL_COUNT,
+                               GFP_KERNEL);
+       if (!hdw->controls) goto fail;
+       memset(hdw->controls,0,sizeof(struct pvr2_ctrl) * CTRL_COUNT);
+       hdw->hdw_type = hdw_type;
+
+       for (idx = 0; idx < 32; idx++) {
+               hdw->std_mask_ptrs[idx] = hdw->std_mask_names[idx];
+       }
+
+       for (idx = 0; idx < CTRL_COUNT; idx++) {
+               cptr = hdw->controls + idx;
+               cptr->hdw = hdw;
+               cptr->info = control_defs+idx;
+       }
+
+       // Initialize video standard enum dynamic control
+       cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDENUM);
+       if (cptr) {
+               memcpy(&hdw->std_info_enum,cptr->info,
+                      sizeof(hdw->std_info_enum));
+               cptr->info = &hdw->std_info_enum;
+
+       }
+       // Initialize control data regarding video standard masks
+       valid_std_mask = pvr2_std_get_usable();
+       for (idx = 0; idx < 32; idx++) {
+               if (!(valid_std_mask & (1 << idx))) continue;
+               cnt1 = pvr2_std_id_to_str(
+                       hdw->std_mask_names[idx],
+                       sizeof(hdw->std_mask_names[idx])-1,
+                       1 << idx);
+               hdw->std_mask_names[idx][cnt1] = 0;
+       }
+       cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDAVAIL);
+       if (cptr) {
+               memcpy(&hdw->std_info_avail,cptr->info,
+                      sizeof(hdw->std_info_avail));
+               cptr->info = &hdw->std_info_avail;
+               hdw->std_info_avail.def.type_bitmask.bit_names =
+                       hdw->std_mask_ptrs;
+               hdw->std_info_avail.def.type_bitmask.valid_bits =
+                       valid_std_mask;
+       }
+       cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR);
+       if (cptr) {
+               memcpy(&hdw->std_info_cur,cptr->info,
+                      sizeof(hdw->std_info_cur));
+               cptr->info = &hdw->std_info_cur;
+               hdw->std_info_cur.def.type_bitmask.bit_names =
+                       hdw->std_mask_ptrs;
+               hdw->std_info_avail.def.type_bitmask.valid_bits =
+                       valid_std_mask;
+       }
+
+       hdw->eeprom_addr = -1;
+       hdw->unit_number = -1;
+       hdw->v4l_minor_number = -1;
+       hdw->ctl_write_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL);
+       if (!hdw->ctl_write_buffer) goto fail;
+       hdw->ctl_read_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL);
+       if (!hdw->ctl_read_buffer) goto fail;
+       hdw->ctl_write_urb = usb_alloc_urb(0,GFP_KERNEL);
+       if (!hdw->ctl_write_urb) goto fail;
+       hdw->ctl_read_urb = usb_alloc_urb(0,GFP_KERNEL);
+       if (!hdw->ctl_read_urb) goto fail;
+
+       down(&pvr2_unit_sem); do {
+               for (idx = 0; idx < PVR_NUM; idx++) {
+                       if (unit_pointers[idx]) continue;
+                       hdw->unit_number = idx;
+                       unit_pointers[idx] = hdw;
+                       break;
+               }
+       } while (0); up(&pvr2_unit_sem);
+
+       cnt1 = 0;
+       cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"pvrusb2");
+       cnt1 += cnt2;
+       if (hdw->unit_number >= 0) {
+               cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"_%c",
+                                ('a' + hdw->unit_number));
+               cnt1 += cnt2;
+       }
+       if (cnt1 >= sizeof(hdw->name)) cnt1 = sizeof(hdw->name)-1;
+       hdw->name[cnt1] = 0;
+
+       pvr2_trace(PVR2_TRACE_INIT,"Driver unit number is %d, name is %s",
+                  hdw->unit_number,hdw->name);
+
+       hdw->tuner_type = -1;
+       hdw->flag_ok = !0;
+       /* Initialize the mask of subsystems that we will shut down when we
+          stop streaming. */
+       hdw->subsys_stream_mask = PVR2_SUBSYS_RUN_ALL;
+       hdw->subsys_stream_mask |= (1<<PVR2_SUBSYS_B_ENC_CFG);
+
+       pvr2_trace(PVR2_TRACE_INIT,"subsys_stream_mask: 0x%lx",
+                  hdw->subsys_stream_mask);
+
+       hdw->usb_intf = intf;
+       hdw->usb_dev = interface_to_usbdev(intf);
+
+       ifnum = hdw->usb_intf->cur_altsetting->desc.bInterfaceNumber;
+       usb_set_interface(hdw->usb_dev,ifnum,0);
+
+       mutex_init(&hdw->ctl_lock_mutex);
+       mutex_init(&hdw->big_lock_mutex);
+
+       return hdw;
+ fail:
+       if (hdw) {
+               if (hdw->ctl_read_urb) usb_free_urb(hdw->ctl_read_urb);
+               if (hdw->ctl_write_urb) usb_free_urb(hdw->ctl_write_urb);
+               if (hdw->ctl_read_buffer) kfree(hdw->ctl_read_buffer);
+               if (hdw->ctl_write_buffer) kfree(hdw->ctl_write_buffer);
+               if (hdw->controls) kfree(hdw->controls);
+               kfree(hdw);
+       }
+       return 0;
+}
+
+
+/* Remove _all_ associations between this driver and the underlying USB
+   layer. */
+void pvr2_hdw_remove_usb_stuff(struct pvr2_hdw *hdw)
+{
+       if (hdw->flag_disconnected) return;
+       pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_remove_usb_stuff: hdw=%p",hdw);
+       if (hdw->ctl_read_urb) {
+               usb_kill_urb(hdw->ctl_read_urb);
+               usb_free_urb(hdw->ctl_read_urb);
+               hdw->ctl_read_urb = 0;
+       }
+       if (hdw->ctl_write_urb) {
+               usb_kill_urb(hdw->ctl_write_urb);
+               usb_free_urb(hdw->ctl_write_urb);
+               hdw->ctl_write_urb = 0;
+       }
+       if (hdw->ctl_read_buffer) {
+               kfree(hdw->ctl_read_buffer);
+               hdw->ctl_read_buffer = 0;
+       }
+       if (hdw->ctl_write_buffer) {
+               kfree(hdw->ctl_write_buffer);
+               hdw->ctl_write_buffer = 0;
+       }
+       pvr2_hdw_render_useless_unlocked(hdw);
+       hdw->flag_disconnected = !0;
+       hdw->usb_dev = 0;
+       hdw->usb_intf = 0;
+}
+
+
+/* Destroy hardware interaction structure */
+void pvr2_hdw_destroy(struct pvr2_hdw *hdw)
+{
+       pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_destroy: hdw=%p",hdw);
+       if (hdw->fw_buffer) {
+               kfree(hdw->fw_buffer);
+               hdw->fw_buffer = 0;
+       }
+       if (hdw->vid_stream) {
+               pvr2_stream_destroy(hdw->vid_stream);
+               hdw->vid_stream = 0;
+       }
+       if (hdw->audio_stat) {
+               hdw->audio_stat->detach(hdw->audio_stat->ctxt);
+       }
+       if (hdw->decoder_ctrl) {
+               hdw->decoder_ctrl->detach(hdw->decoder_ctrl->ctxt);
+       }
+       pvr2_i2c_core_done(hdw);
+       pvr2_hdw_remove_usb_stuff(hdw);
+       down(&pvr2_unit_sem); do {
+               if ((hdw->unit_number >= 0) &&
+                   (hdw->unit_number < PVR_NUM) &&
+                   (unit_pointers[hdw->unit_number] == hdw)) {
+                       unit_pointers[hdw->unit_number] = 0;
+               }
+       } while (0); up(&pvr2_unit_sem);
+       kfree(hdw->controls);
+       if (hdw->std_defs) kfree(hdw->std_defs);
+       if (hdw->std_enum_names) kfree(hdw->std_enum_names);
+       kfree(hdw);
+}
+
+
+int pvr2_hdw_init_ok(struct pvr2_hdw *hdw)
+{
+       return hdw->flag_init_ok;
+}
+
+
+int pvr2_hdw_dev_ok(struct pvr2_hdw *hdw)
+{
+       return (hdw && hdw->flag_ok);
+}
+
+
+/* Called when hardware has been unplugged */
+void pvr2_hdw_disconnect(struct pvr2_hdw *hdw)
+{
+       pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_disconnect(hdw=%p)",hdw);
+       LOCK_TAKE(hdw->big_lock);
+       LOCK_TAKE(hdw->ctl_lock);
+       pvr2_hdw_remove_usb_stuff(hdw);
+       LOCK_GIVE(hdw->ctl_lock);
+       LOCK_GIVE(hdw->big_lock);
+}
+
+
+// Attempt to autoselect an appropriate value for std_enum_cur given
+// whatever is currently in std_mask_cur
+void pvr2_hdw_internal_find_stdenum(struct pvr2_hdw *hdw)
+{
+       unsigned int idx;
+       for (idx = 1; idx < hdw->std_enum_cnt; idx++) {
+               if (hdw->std_defs[idx-1].id == hdw->std_mask_cur) {
+                       hdw->std_enum_cur = idx;
+                       return;
+               }
+       }
+       hdw->std_enum_cur = 0;
+}
+
+
+// Calculate correct set of enumerated standards based on currently known
+// set of available standards bits.
+void pvr2_hdw_internal_set_std_avail(struct pvr2_hdw *hdw)
+{
+       struct v4l2_standard *newstd;
+       unsigned int std_cnt;
+       unsigned int idx;
+
+       newstd = pvr2_std_create_enum(&std_cnt,hdw->std_mask_avail);
+
+       if (hdw->std_defs) {
+               kfree(hdw->std_defs);
+               hdw->std_defs = 0;
+       }
+       hdw->std_enum_cnt = 0;
+       if (hdw->std_enum_names) {
+               kfree(hdw->std_enum_names);
+               hdw->std_enum_names = 0;
+       }
+
+       if (!std_cnt) {
+               pvr2_trace(
+                       PVR2_TRACE_ERROR_LEGS,
+                       "WARNING: Failed to identify any viable standards");
+       }
+       hdw->std_enum_names = kmalloc(sizeof(char *)*(std_cnt+1),GFP_KERNEL);
+       hdw->std_enum_names[0] = "none";
+       for (idx = 0; idx < std_cnt; idx++) {
+               hdw->std_enum_names[idx+1] =
+                       newstd[idx].name;
+       }
+       // Set up the dynamic control for this standard
+       hdw->std_info_enum.def.type_enum.value_names = hdw->std_enum_names;
+       hdw->std_info_enum.def.type_enum.count = std_cnt+1;
+       hdw->std_defs = newstd;
+       hdw->std_enum_cnt = std_cnt+1;
+       hdw->std_enum_cur = 0;
+       hdw->std_info_cur.def.type_bitmask.valid_bits = hdw->std_mask_avail;
+}
+
+
+int pvr2_hdw_get_stdenum_value(struct pvr2_hdw *hdw,
+                              struct v4l2_standard *std,
+                              unsigned int idx)
+{
+       int ret = -EINVAL;
+       if (!idx) return ret;
+       LOCK_TAKE(hdw->big_lock); do {
+               if (idx >= hdw->std_enum_cnt) break;
+               idx--;
+               memcpy(std,hdw->std_defs+idx,sizeof(*std));
+               ret = 0;
+       } while (0); LOCK_GIVE(hdw->big_lock);
+       return ret;
+}
+
+
+/* Get the number of defined controls */
+unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *hdw)
+{
+       return CTRL_COUNT;
+}
+
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *hdw,
+                                            unsigned int idx)
+{
+       if (idx >= CTRL_COUNT) return 0;
+       return hdw->controls + idx;
+}
+
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *hdw,
+                                         unsigned int ctl_id)
+{
+       struct pvr2_ctrl *cptr;
+       unsigned int idx;
+       int i;
+
+       /* This could be made a lot more efficient, but for now... */
+       for (idx = 0; idx < CTRL_COUNT; idx++) {
+               cptr = hdw->controls + idx;
+               i = cptr->info->internal_id;
+               if (i && (i == ctl_id)) return cptr;
+       }
+       return 0;
+}
+
+
+/* Given an ID, retrieve the control structure associated with it. */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *hdw,unsigned int ctl_id)
+{
+       struct pvr2_ctrl *cptr;
+       unsigned int idx;
+       int i;
+
+       /* This could be made a lot more efficient, but for now... */
+       for (idx = 0; idx < CTRL_COUNT; idx++) {
+               cptr = hdw->controls + idx;
+               i = cptr->info->v4l_id;
+               if (i && (i == ctl_id)) return cptr;
+       }
+       return 0;
+}
+
+
+static const char *get_ctrl_typename(enum pvr2_ctl_type tp)
+{
+       switch (tp) {
+       case pvr2_ctl_int: return "integer";
+       case pvr2_ctl_enum: return "enum";
+       case pvr2_ctl_bitmask: return "bitmask";
+       }
+       return "";
+}
+
+
+/* Commit all control changes made up to this point.  Subsystems can be
+   indirectly affected by these changes.  For a given set of things being
+   committed, we'll clear the affected subsystem bits and then once we're
+   done committing everything we'll make a request to restore the subsystem
+   state(s) back to their previous value before this function was called.
+   Thus we can automatically reconfigure affected pieces of the driver as
+   controls are changed. */
+int pvr2_hdw_commit_ctl_internal(struct pvr2_hdw *hdw)
+{
+       unsigned long saved_subsys_mask = hdw->subsys_enabled_mask;
+       unsigned long stale_subsys_mask = 0;
+       unsigned int idx;
+       struct pvr2_ctrl *cptr;
+       int value;
+       int commit_flag = 0;
+       char buf[100];
+       unsigned int bcnt,ccnt;
+
+       for (idx = 0; idx < CTRL_COUNT; idx++) {
+               cptr = hdw->controls + idx;
+               if (cptr->info->is_dirty == 0) continue;
+               if (!cptr->info->is_dirty(cptr)) continue;
+               if (!commit_flag) {
+                       commit_flag = !0;
+               }
+
+               bcnt = scnprintf(buf,sizeof(buf),"\"%s\" <-- ",
+                                cptr->info->name);
+               value = 0;
+               cptr->info->get_value(cptr,&value);
+               pvr2_ctrl_value_to_sym_internal(cptr,~0,value,
+                                               buf+bcnt,
+                                               sizeof(buf)-bcnt,&ccnt);
+               bcnt += ccnt;
+               bcnt += scnprintf(buf+bcnt,sizeof(buf)-bcnt," <%s>",
+                                 get_ctrl_typename(cptr->info->type));
+               pvr2_trace(PVR2_TRACE_CTL,
+                          "/*--TRACE_COMMIT--*/ %.*s",
+                          bcnt,buf);
+       }
+
+       if (!commit_flag) {
+               /* Nothing has changed */
+               return 0;
+       }
+
+       /* When video standard changes, reset the hres and vres values -
+          but if the user has pending changes there, then let the changes
+          take priority. */
+       if (hdw->std_dirty) {
+               /* Rewrite the vertical resolution to be appropriate to the
+                  video standard that has been selected. */
+               int nvres;
+               if (hdw->std_mask_cur & V4L2_STD_525_60) {
+                       nvres = 480;
+               } else {
+                       nvres = 576;
+               }
+               if (nvres != hdw->res_ver_val) {
+                       hdw->res_ver_val = nvres;
+                       hdw->res_ver_dirty = !0;
+               }
+               if (!hdw->interlace_val) {
+                       hdw->interlace_val = 0;
+                       hdw->interlace_dirty = !0;
+               }
+       }
+
+       if (hdw->std_dirty ||
+           hdw->res_ver_dirty ||
+           hdw->res_hor_dirty ||
+           hdw->interlace_dirty ||
+           hdw->vbr_dirty ||
+           hdw->videobitrate_dirty ||
+           hdw->videopeak_dirty ||
+           hdw->audiobitrate_dirty ||
+           hdw->srate_dirty ||
+           hdw->audiolayer_dirty ||
+           hdw->audiocrc_dirty ||
+           hdw->audioemphasis_dirty) {
+               /* If any of this changes, then the encoder needs to be
+                  reconfigured, and we need to reset the stream. */
+               stale_subsys_mask |= (1<<PVR2_SUBSYS_B_ENC_CFG);
+               stale_subsys_mask |= hdw->subsys_stream_mask;
+       }
+
+       /* Scan i2c core at this point - before we clear all the dirty
+          bits.  Various parts of the i2c core will notice dirty bits as
+          appropriate and arrange to broadcast or directly send updates to
+          the client drivers in order to keep everything in sync */
+       pvr2_i2c_core_check_stale(hdw);
+
+       for (idx = 0; idx < CTRL_COUNT; idx++) {
+               cptr = hdw->controls + idx;
+               if (!cptr->info->clear_dirty) continue;
+               cptr->info->clear_dirty(cptr);
+       }
+
+       /* Now execute i2c core update */
+       pvr2_i2c_core_sync(hdw);
+
+       pvr2_hdw_subsys_bit_chg_no_lock(hdw,stale_subsys_mask,0);
+       pvr2_hdw_subsys_bit_chg_no_lock(hdw,~0,saved_subsys_mask);
+
+       return 0;
+}
+
+
+int pvr2_hdw_commit_ctl(struct pvr2_hdw *hdw)
+{
+       LOCK_TAKE(hdw->big_lock); do {
+               pvr2_hdw_commit_ctl_internal(hdw);
+       } while (0); LOCK_GIVE(hdw->big_lock);
+       return 0;
+}
+
+
+void pvr2_hdw_poll(struct pvr2_hdw *hdw)
+{
+       LOCK_TAKE(hdw->big_lock); do {
+               pvr2_i2c_core_sync(hdw);
+       } while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+void pvr2_hdw_setup_poll_trigger(struct pvr2_hdw *hdw,
+                                void (*func)(void *),
+                                void *data)
+{
+       LOCK_TAKE(hdw->big_lock); do {
+               hdw->poll_trigger_func = func;
+               hdw->poll_trigger_data = data;
+       } while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+void pvr2_hdw_poll_trigger_unlocked(struct pvr2_hdw *hdw)
+{
+       if (hdw->poll_trigger_func) {
+               hdw->poll_trigger_func(hdw->poll_trigger_data);
+       }
+}
+
+
+void pvr2_hdw_poll_trigger(struct pvr2_hdw *hdw)
+{
+       LOCK_TAKE(hdw->big_lock); do {
+               pvr2_hdw_poll_trigger_unlocked(hdw);
+       } while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+/* Return name for this driver instance */
+const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *hdw)
+{
+       return hdw->name;
+}
+
+
+/* Return bit mask indicating signal status */
+unsigned int pvr2_hdw_get_signal_status_internal(struct pvr2_hdw *hdw)
+{
+       unsigned int msk = 0;
+       switch (hdw->input_val) {
+       case PVR2_CVAL_INPUT_TV:
+       case PVR2_CVAL_INPUT_RADIO:
+               if (hdw->decoder_ctrl &&
+                   hdw->decoder_ctrl->tuned(hdw->decoder_ctrl->ctxt)) {
+                       msk |= PVR2_SIGNAL_OK;
+                       if (hdw->audio_stat &&
+                           hdw->audio_stat->status(hdw->audio_stat->ctxt)) {
+                               if (hdw->flag_stereo) {
+                                       msk |= PVR2_SIGNAL_STEREO;
+                               }
+                               if (hdw->flag_bilingual) {
+                                       msk |= PVR2_SIGNAL_SAP;
+                               }
+                       }
+               }
+               break;
+       default:
+               msk |= PVR2_SIGNAL_OK | PVR2_SIGNAL_STEREO;
+       }
+       return msk;
+}
+
+
+int pvr2_hdw_is_hsm(struct pvr2_hdw *hdw)
+{
+       int result;
+       LOCK_TAKE(hdw->ctl_lock); do {
+               hdw->cmd_buffer[0] = 0x0b;
+               result = pvr2_send_request(hdw,
+                                          hdw->cmd_buffer,1,
+                                          hdw->cmd_buffer,1);
+               if (result < 0) break;
+               result = (hdw->cmd_buffer[0] != 0);
+       } while(0); LOCK_GIVE(hdw->ctl_lock);
+       return result;
+}
+
+
+/* Return bit mask indicating signal status */
+unsigned int pvr2_hdw_get_signal_status(struct pvr2_hdw *hdw)
+{
+       unsigned int msk = 0;
+       LOCK_TAKE(hdw->big_lock); do {
+               msk = pvr2_hdw_get_signal_status_internal(hdw);
+       } while (0); LOCK_GIVE(hdw->big_lock);
+       return msk;
+}
+
+
+/* Get handle to video output stream */
+struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *hp)
+{
+       return hp->vid_stream;
+}
+
+
+void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw)
+{
+       LOCK_TAKE(hdw->big_lock); do {
+               hdw->log_requested = !0;
+               pvr2_i2c_core_check_stale(hdw);
+               hdw->log_requested = 0;
+               pvr2_i2c_core_sync(hdw);
+       } while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *hdw, int enable_flag)
+{
+       int ret;
+       u16 address;
+       unsigned int pipe;
+       LOCK_TAKE(hdw->big_lock); do {
+               if ((hdw->fw_buffer == 0) == !enable_flag) break;
+
+               if (!enable_flag) {
+                       pvr2_trace(PVR2_TRACE_FIRMWARE,
+                                  "Cleaning up after CPU firmware fetch");
+                       kfree(hdw->fw_buffer);
+                       hdw->fw_buffer = 0;
+                       hdw->fw_size = 0;
+                       /* Now release the CPU.  It will disconnect and
+                          reconnect later. */
+                       pvr2_hdw_cpureset_assert(hdw,0);
+                       break;
+               }
+
+               pvr2_trace(PVR2_TRACE_FIRMWARE,
+                          "Preparing to suck out CPU firmware");
+               hdw->fw_size = 0x2000;
+               hdw->fw_buffer = kmalloc(hdw->fw_size,GFP_KERNEL);
+               if (!hdw->fw_buffer) {
+                       hdw->fw_size = 0;
+                       break;
+               }
+
+               memset(hdw->fw_buffer,0,hdw->fw_size);
+
+               /* We have to hold the CPU during firmware upload. */
+               pvr2_hdw_cpureset_assert(hdw,1);
+
+               /* download the firmware from address 0000-1fff in 2048
+                  (=0x800) bytes chunk. */
+
+               pvr2_trace(PVR2_TRACE_FIRMWARE,"Grabbing CPU firmware");
+               pipe = usb_rcvctrlpipe(hdw->usb_dev, 0);
+               for(address = 0; address < hdw->fw_size; address += 0x800) {
+                       ret = usb_control_msg(hdw->usb_dev,pipe,0xa0,0xc0,
+                                             address,0,
+                                             hdw->fw_buffer+address,0x800,HZ);
+                       if (ret < 0) break;
+               }
+
+               pvr2_trace(PVR2_TRACE_FIRMWARE,"Done grabbing CPU firmware");
+
+       } while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+/* Return true if we're in a mode for retrieval CPU firmware */
+int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *hdw)
+{
+       return hdw->fw_buffer != 0;
+}
+
+
+int pvr2_hdw_cpufw_get(struct pvr2_hdw *hdw,unsigned int offs,
+                      char *buf,unsigned int cnt)
+{
+       int ret = -EINVAL;
+       LOCK_TAKE(hdw->big_lock); do {
+               if (!buf) break;
+               if (!cnt) break;
+
+               if (!hdw->fw_buffer) {
+                       ret = -EIO;
+                       break;
+               }
+
+               if (offs >= hdw->fw_size) {
+                       pvr2_trace(PVR2_TRACE_FIRMWARE,
+                                  "Read firmware data offs=%d EOF",
+                                  offs);
+                       ret = 0;
+                       break;
+               }
+
+               if (offs + cnt > hdw->fw_size) cnt = hdw->fw_size - offs;
+
+               memcpy(buf,hdw->fw_buffer+offs,cnt);
+
+               pvr2_trace(PVR2_TRACE_FIRMWARE,
+                          "Read firmware data offs=%d cnt=%d",
+                          offs,cnt);
+               ret = cnt;
+       } while (0); LOCK_GIVE(hdw->big_lock);
+
+       return ret;
+}
+
+
+int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *hdw)
+{
+       return hdw->v4l_minor_number;
+}
+
+
+/* Store the v4l minor device number */
+void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *hdw,int v)
+{
+       hdw->v4l_minor_number = v;
+}
+
+
+void pvr2_reset_ctl_endpoints(struct pvr2_hdw *hdw)
+{
+       if (!hdw->usb_dev) return;
+       usb_settoggle(hdw->usb_dev, PVR2_CTL_WRITE_ENDPOINT & 0xf,
+                     !(PVR2_CTL_WRITE_ENDPOINT & USB_DIR_IN), 0);
+       usb_settoggle(hdw->usb_dev, PVR2_CTL_READ_ENDPOINT & 0xf,
+                     !(PVR2_CTL_READ_ENDPOINT & USB_DIR_IN), 0);
+       usb_clear_halt(hdw->usb_dev,
+                      usb_rcvbulkpipe(hdw->usb_dev,
+                                      PVR2_CTL_READ_ENDPOINT & 0x7f));
+       usb_clear_halt(hdw->usb_dev,
+                      usb_sndbulkpipe(hdw->usb_dev,
+                                      PVR2_CTL_WRITE_ENDPOINT & 0x7f));
+}
+
+
+static void pvr2_ctl_write_complete(struct urb *urb, struct pt_regs *regs)
+{
+       struct pvr2_hdw *hdw = urb->context;
+       hdw->ctl_write_pend_flag = 0;
+       if (hdw->ctl_read_pend_flag) return;
+       complete(&hdw->ctl_done);
+}
+
+
+static void pvr2_ctl_read_complete(struct urb *urb, struct pt_regs *regs)
+{
+       struct pvr2_hdw *hdw = urb->context;
+       hdw->ctl_read_pend_flag = 0;
+       if (hdw->ctl_write_pend_flag) return;
+       complete(&hdw->ctl_done);
+}
+
+
+static void pvr2_ctl_timeout(unsigned long data)
+{
+       struct pvr2_hdw *hdw = (struct pvr2_hdw *)data;
+       if (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) {
+               hdw->ctl_timeout_flag = !0;
+               if (hdw->ctl_write_pend_flag && hdw->ctl_write_urb) {
+                       usb_unlink_urb(hdw->ctl_write_urb);
+               }
+               if (hdw->ctl_read_pend_flag && hdw->ctl_read_urb) {
+                       usb_unlink_urb(hdw->ctl_read_urb);
+               }
+       }
+}
+
+
+int pvr2_send_request_ex(struct pvr2_hdw *hdw,
+                        unsigned int timeout,int probe_fl,
+                        void *write_data,unsigned int write_len,
+                        void *read_data,unsigned int read_len)
+{
+       unsigned int idx;
+       int status = 0;
+       struct timer_list timer;
+       if (!hdw->ctl_lock_held) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Attempted to execute control transfer"
+                          " without lock!!");
+               return -EDEADLK;
+       }
+       if ((!hdw->flag_ok) && !probe_fl) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Attempted to execute control transfer"
+                          " when device not ok");
+               return -EIO;
+       }
+       if (!(hdw->ctl_read_urb && hdw->ctl_write_urb)) {
+               if (!probe_fl) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "Attempted to execute control transfer"
+                                  " when USB is disconnected");
+               }
+               return -ENOTTY;
+       }
+
+       /* Ensure that we have sane parameters */
+       if (!write_data) write_len = 0;
+       if (!read_data) read_len = 0;
+       if (write_len > PVR2_CTL_BUFFSIZE) {
+               pvr2_trace(
+                       PVR2_TRACE_ERROR_LEGS,
+                       "Attempted to execute %d byte"
+                       " control-write transfer (limit=%d)",
+                       write_len,PVR2_CTL_BUFFSIZE);
+               return -EINVAL;
+       }
+       if (read_len > PVR2_CTL_BUFFSIZE) {
+               pvr2_trace(
+                       PVR2_TRACE_ERROR_LEGS,
+                       "Attempted to execute %d byte"
+                       " control-read transfer (limit=%d)",
+                       write_len,PVR2_CTL_BUFFSIZE);
+               return -EINVAL;
+       }
+       if ((!write_len) && (!read_len)) {
+               pvr2_trace(
+                       PVR2_TRACE_ERROR_LEGS,
+                       "Attempted to execute null control transfer?");
+               return -EINVAL;
+       }
+
+
+       hdw->cmd_debug_state = 1;
+       if (write_len) {
+               hdw->cmd_debug_code = ((unsigned char *)write_data)[0];
+       } else {
+               hdw->cmd_debug_code = 0;
+       }
+       hdw->cmd_debug_write_len = write_len;
+       hdw->cmd_debug_read_len = read_len;
+
+       /* Initialize common stuff */
+       init_completion(&hdw->ctl_done);
+       hdw->ctl_timeout_flag = 0;
+       hdw->ctl_write_pend_flag = 0;
+       hdw->ctl_read_pend_flag = 0;
+       init_timer(&timer);
+       timer.expires = jiffies + timeout;
+       timer.data = (unsigned long)hdw;
+       timer.function = pvr2_ctl_timeout;
+
+       if (write_len) {
+               hdw->cmd_debug_state = 2;
+               /* Transfer write data to internal buffer */
+               for (idx = 0; idx < write_len; idx++) {
+                       hdw->ctl_write_buffer[idx] =
+                               ((unsigned char *)write_data)[idx];
+               }
+               /* Initiate a write request */
+               usb_fill_bulk_urb(hdw->ctl_write_urb,
+                                 hdw->usb_dev,
+                                 usb_sndbulkpipe(hdw->usb_dev,
+                                                 PVR2_CTL_WRITE_ENDPOINT),
+                                 hdw->ctl_write_buffer,
+                                 write_len,
+                                 pvr2_ctl_write_complete,
+                                 hdw);
+               hdw->ctl_write_urb->actual_length = 0;
+               hdw->ctl_write_pend_flag = !0;
+               status = usb_submit_urb(hdw->ctl_write_urb,GFP_KERNEL);
+               if (status < 0) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "Failed to submit write-control"
+                                  " URB status=%d",status);
+                       hdw->ctl_write_pend_flag = 0;
+                       goto done;
+               }
+       }
+
+       if (read_len) {
+               hdw->cmd_debug_state = 3;
+               memset(hdw->ctl_read_buffer,0x43,read_len);
+               /* Initiate a read request */
+               usb_fill_bulk_urb(hdw->ctl_read_urb,
+                                 hdw->usb_dev,
+                                 usb_rcvbulkpipe(hdw->usb_dev,
+                                                 PVR2_CTL_READ_ENDPOINT),
+                                 hdw->ctl_read_buffer,
+                                 read_len,
+                                 pvr2_ctl_read_complete,
+                                 hdw);
+               hdw->ctl_read_urb->actual_length = 0;
+               hdw->ctl_read_pend_flag = !0;
+               status = usb_submit_urb(hdw->ctl_read_urb,GFP_KERNEL);
+               if (status < 0) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "Failed to submit read-control"
+                                  " URB status=%d",status);
+                       hdw->ctl_read_pend_flag = 0;
+                       goto done;
+               }
+       }
+
+       /* Start timer */
+       add_timer(&timer);
+
+       /* Now wait for all I/O to complete */
+       hdw->cmd_debug_state = 4;
+       while (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) {
+               wait_for_completion(&hdw->ctl_done);
+       }
+       hdw->cmd_debug_state = 5;
+
+       /* Stop timer */
+       del_timer_sync(&timer);
+
+       hdw->cmd_debug_state = 6;
+       status = 0;
+
+       if (hdw->ctl_timeout_flag) {
+               status = -ETIMEDOUT;
+               if (!probe_fl) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "Timed out control-write");
+               }
+               goto done;
+       }
+
+       if (write_len) {
+               /* Validate results of write request */
+               if ((hdw->ctl_write_urb->status != 0) &&
+                   (hdw->ctl_write_urb->status != -ENOENT) &&
+                   (hdw->ctl_write_urb->status != -ESHUTDOWN) &&
+                   (hdw->ctl_write_urb->status != -ECONNRESET)) {
+                       /* USB subsystem is reporting some kind of failure
+                          on the write */
+                       status = hdw->ctl_write_urb->status;
+                       if (!probe_fl) {
+                               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                          "control-write URB failure,"
+                                          " status=%d",
+                                          status);
+                       }
+                       goto done;
+               }
+               if (hdw->ctl_write_urb->actual_length < write_len) {
+                       /* Failed to write enough data */
+                       status = -EIO;
+                       if (!probe_fl) {
+                               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                          "control-write URB short,"
+                                          " expected=%d got=%d",
+                                          write_len,
+                                          hdw->ctl_write_urb->actual_length);
+                       }
+                       goto done;
+               }
+       }
+       if (read_len) {
+               /* Validate results of read request */
+               if ((hdw->ctl_read_urb->status != 0) &&
+                   (hdw->ctl_read_urb->status != -ENOENT) &&
+                   (hdw->ctl_read_urb->status != -ESHUTDOWN) &&
+                   (hdw->ctl_read_urb->status != -ECONNRESET)) {
+                       /* USB subsystem is reporting some kind of failure
+                          on the read */
+                       status = hdw->ctl_read_urb->status;
+                       if (!probe_fl) {
+                               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                          "control-read URB failure,"
+                                          " status=%d",
+                                          status);
+                       }
+                       goto done;
+               }
+               if (hdw->ctl_read_urb->actual_length < read_len) {
+                       /* Failed to read enough data */
+                       status = -EIO;
+                       if (!probe_fl) {
+                               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                          "control-read URB short,"
+                                          " expected=%d got=%d",
+                                          read_len,
+                                          hdw->ctl_read_urb->actual_length);
+                       }
+                       goto done;
+               }
+               /* Transfer retrieved data out from internal buffer */
+               for (idx = 0; idx < read_len; idx++) {
+                       ((unsigned char *)read_data)[idx] =
+                               hdw->ctl_read_buffer[idx];
+               }
+       }
+
+ done:
+
+       hdw->cmd_debug_state = 0;
+       if ((status < 0) && (!probe_fl)) {
+               pvr2_hdw_render_useless_unlocked(hdw);
+       }
+       return status;
+}
+
+
+int pvr2_send_request(struct pvr2_hdw *hdw,
+                     void *write_data,unsigned int write_len,
+                     void *read_data,unsigned int read_len)
+{
+       return pvr2_send_request_ex(hdw,HZ*4,0,
+                                   write_data,write_len,
+                                   read_data,read_len);
+}
+
+int pvr2_write_register(struct pvr2_hdw *hdw, u16 reg, u32 data)
+{
+       int ret;
+
+       LOCK_TAKE(hdw->ctl_lock);
+
+       hdw->cmd_buffer[0] = 0x04;  /* write register prefix */
+       PVR2_DECOMPOSE_LE(hdw->cmd_buffer,1,data);
+       hdw->cmd_buffer[5] = 0;
+       hdw->cmd_buffer[6] = (reg >> 8) & 0xff;
+       hdw->cmd_buffer[7] = reg & 0xff;
+
+
+       ret = pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 0);
+
+       LOCK_GIVE(hdw->ctl_lock);
+
+       return ret;
+}
+
+
+int pvr2_read_register(struct pvr2_hdw *hdw, u16 reg, u32 *data)
+{
+       int ret = 0;
+
+       LOCK_TAKE(hdw->ctl_lock);
+
+       hdw->cmd_buffer[0] = 0x05;  /* read register prefix */
+       hdw->cmd_buffer[1] = 0;
+       hdw->cmd_buffer[2] = 0;
+       hdw->cmd_buffer[3] = 0;
+       hdw->cmd_buffer[4] = 0;
+       hdw->cmd_buffer[5] = 0;
+       hdw->cmd_buffer[6] = (reg >> 8) & 0xff;
+       hdw->cmd_buffer[7] = reg & 0xff;
+
+       ret |= pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 4);
+       *data = PVR2_COMPOSE_LE(hdw->cmd_buffer,0);
+
+       LOCK_GIVE(hdw->ctl_lock);
+
+       return ret;
+}
+
+
+int pvr2_write_u16(struct pvr2_hdw *hdw, u16 data, int res)
+{
+       int ret;
+
+       LOCK_TAKE(hdw->ctl_lock);
+
+       hdw->cmd_buffer[0] = (data >> 8) & 0xff;
+       hdw->cmd_buffer[1] = data & 0xff;
+
+       ret = pvr2_send_request(hdw, hdw->cmd_buffer, 2, hdw->cmd_buffer, res);
+
+       LOCK_GIVE(hdw->ctl_lock);
+
+       return ret;
+}
+
+
+int pvr2_write_u8(struct pvr2_hdw *hdw, u8 data, int res)
+{
+       int ret;
+
+       LOCK_TAKE(hdw->ctl_lock);
+
+       hdw->cmd_buffer[0] = data;
+
+       ret = pvr2_send_request(hdw, hdw->cmd_buffer, 1, hdw->cmd_buffer, res);
+
+       LOCK_GIVE(hdw->ctl_lock);
+
+       return ret;
+}
+
+
+void pvr2_hdw_render_useless_unlocked(struct pvr2_hdw *hdw)
+{
+       if (!hdw->flag_ok) return;
+       pvr2_trace(PVR2_TRACE_INIT,"render_useless");
+       hdw->flag_ok = 0;
+       if (hdw->vid_stream) {
+               pvr2_stream_setup(hdw->vid_stream,0,0,0);
+       }
+       hdw->flag_streaming_enabled = 0;
+       hdw->subsys_enabled_mask = 0;
+}
+
+
+void pvr2_hdw_render_useless(struct pvr2_hdw *hdw)
+{
+       LOCK_TAKE(hdw->ctl_lock);
+       pvr2_hdw_render_useless_unlocked(hdw);
+       LOCK_GIVE(hdw->ctl_lock);
+}
+
+
+void pvr2_hdw_device_reset(struct pvr2_hdw *hdw)
+{
+       int ret;
+       pvr2_trace(PVR2_TRACE_INIT,"Performing a device reset...");
+       ret = usb_lock_device_for_reset(hdw->usb_dev,0);
+       if (ret == 1) {
+               ret = usb_reset_device(hdw->usb_dev);
+               usb_unlock_device(hdw->usb_dev);
+       } else {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Failed to lock USB device ret=%d",ret);
+       }
+       if (init_pause_msec) {
+               pvr2_trace(PVR2_TRACE_INFO,
+                          "Waiting %u msec for hardware to settle",
+                          init_pause_msec);
+               msleep(init_pause_msec);
+       }
+
+}
+
+
+void pvr2_hdw_cpureset_assert(struct pvr2_hdw *hdw,int val)
+{
+       char da[1];
+       unsigned int pipe;
+       int ret;
+
+       if (!hdw->usb_dev) return;
+
+       pvr2_trace(PVR2_TRACE_INIT,"cpureset_assert(%d)",val);
+
+       da[0] = val ? 0x01 : 0x00;
+
+       /* Write the CPUCS register on the 8051.  The lsb of the register
+          is the reset bit; a 1 asserts reset while a 0 clears it. */
+       pipe = usb_sndctrlpipe(hdw->usb_dev, 0);
+       ret = usb_control_msg(hdw->usb_dev,pipe,0xa0,0x40,0xe600,0,da,1,HZ);
+       if (ret < 0) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "cpureset_assert(%d) error=%d",val,ret);
+               pvr2_hdw_render_useless(hdw);
+       }
+}
+
+
+int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *hdw)
+{
+       int status;
+       LOCK_TAKE(hdw->ctl_lock); do {
+               pvr2_trace(PVR2_TRACE_INIT,"Requesting uproc hard reset");
+               hdw->flag_ok = !0;
+               hdw->cmd_buffer[0] = 0xdd;
+               status = pvr2_send_request(hdw,hdw->cmd_buffer,1,0,0);
+       } while (0); LOCK_GIVE(hdw->ctl_lock);
+       return status;
+}
+
+
+int pvr2_hdw_cmd_powerup(struct pvr2_hdw *hdw)
+{
+       int status;
+       LOCK_TAKE(hdw->ctl_lock); do {
+               pvr2_trace(PVR2_TRACE_INIT,"Requesting powerup");
+               hdw->cmd_buffer[0] = 0xde;
+               status = pvr2_send_request(hdw,hdw->cmd_buffer,1,0,0);
+       } while (0); LOCK_GIVE(hdw->ctl_lock);
+       return status;
+}
+
+
+int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *hdw)
+{
+       if (!hdw->decoder_ctrl) {
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "Unable to reset decoder: nothing attached");
+               return -ENOTTY;
+       }
+
+       if (!hdw->decoder_ctrl->force_reset) {
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "Unable to reset decoder: not implemented");
+               return -ENOTTY;
+       }
+
+       pvr2_trace(PVR2_TRACE_INIT,
+                  "Requesting decoder reset");
+       hdw->decoder_ctrl->force_reset(hdw->decoder_ctrl->ctxt);
+       return 0;
+}
+
+
+int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl)
+{
+       int status;
+       LOCK_TAKE(hdw->ctl_lock); do {
+               hdw->cmd_buffer[0] = (runFl ? 0x36 : 0x37);
+               status = pvr2_send_request(hdw,hdw->cmd_buffer,1,0,0);
+       } while (0); LOCK_GIVE(hdw->ctl_lock);
+       if (!status) {
+               hdw->subsys_enabled_mask =
+                       ((hdw->subsys_enabled_mask &
+                         ~(1<<PVR2_SUBSYS_B_USBSTREAM_RUN)) |
+                        (runFl ? (1<<PVR2_SUBSYS_B_USBSTREAM_RUN) : 0));
+       }
+       return status;
+}
+
+
+void pvr2_hdw_get_debug_info(const struct pvr2_hdw *hdw,
+                            struct pvr2_hdw_debug_info *ptr)
+{
+       ptr->big_lock_held = hdw->big_lock_held;
+       ptr->ctl_lock_held = hdw->ctl_lock_held;
+       ptr->flag_ok = hdw->flag_ok;
+       ptr->flag_disconnected = hdw->flag_disconnected;
+       ptr->flag_init_ok = hdw->flag_init_ok;
+       ptr->flag_streaming_enabled = hdw->flag_streaming_enabled;
+       ptr->subsys_flags = hdw->subsys_enabled_mask;
+       ptr->cmd_debug_state = hdw->cmd_debug_state;
+       ptr->cmd_code = hdw->cmd_debug_code;
+       ptr->cmd_debug_write_len = hdw->cmd_debug_write_len;
+       ptr->cmd_debug_read_len = hdw->cmd_debug_read_len;
+       ptr->cmd_debug_timeout = hdw->ctl_timeout_flag;
+       ptr->cmd_debug_write_pend = hdw->ctl_write_pend_flag;
+       ptr->cmd_debug_read_pend = hdw->ctl_read_pend_flag;
+       ptr->cmd_debug_rstatus = hdw->ctl_read_urb->status;
+       ptr->cmd_debug_wstatus = hdw->ctl_read_urb->status;
+}
+
+
+int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *dp)
+{
+       return pvr2_read_register(hdw,PVR2_GPIO_DIR,dp);
+}
+
+
+int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *dp)
+{
+       return pvr2_read_register(hdw,PVR2_GPIO_OUT,dp);
+}
+
+
+int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *dp)
+{
+       return pvr2_read_register(hdw,PVR2_GPIO_IN,dp);
+}
+
+
+int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val)
+{
+       u32 cval,nval;
+       int ret;
+       if (~msk) {
+               ret = pvr2_read_register(hdw,PVR2_GPIO_DIR,&cval);
+               if (ret) return ret;
+               nval = (cval & ~msk) | (val & msk);
+               pvr2_trace(PVR2_TRACE_GPIO,
+                          "GPIO direction changing 0x%x:0x%x"
+                          " from 0x%x to 0x%x",
+                          msk,val,cval,nval);
+       } else {
+               nval = val;
+               pvr2_trace(PVR2_TRACE_GPIO,
+                          "GPIO direction changing to 0x%x",nval);
+       }
+       return pvr2_write_register(hdw,PVR2_GPIO_DIR,nval);
+}
+
+
+int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val)
+{
+       u32 cval,nval;
+       int ret;
+       if (~msk) {
+               ret = pvr2_read_register(hdw,PVR2_GPIO_OUT,&cval);
+               if (ret) return ret;
+               nval = (cval & ~msk) | (val & msk);
+               pvr2_trace(PVR2_TRACE_GPIO,
+                          "GPIO output changing 0x%x:0x%x from 0x%x to 0x%x",
+                          msk,val,cval,nval);
+       } else {
+               nval = val;
+               pvr2_trace(PVR2_TRACE_GPIO,
+                          "GPIO output changing to 0x%x",nval);
+       }
+       return pvr2_write_register(hdw,PVR2_GPIO_OUT,nval);
+}
+
+
+int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw)
+{
+       int result;
+       LOCK_TAKE(hdw->ctl_lock); do {
+               hdw->cmd_buffer[0] = 0xeb;
+               result = pvr2_send_request(hdw,
+                                          hdw->cmd_buffer,1,
+                                          hdw->cmd_buffer,1);
+               if (result < 0) break;
+               result = hdw->cmd_buffer[0];
+       } while(0); LOCK_GIVE(hdw->ctl_lock);
+       return result;
+}
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.h b/drivers/media/video/pvrusb2/pvrusb2-hdw.h
new file mode 100644 (file)
index 0000000..e9adef9
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_HDW_H
+#define __PVRUSB2_HDW_H
+
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include "pvrusb2-io.h"
+#include "pvrusb2-ctrl.h"
+
+/* Private V4L2-compatible controls available in this driver, look these up
+   with pvr2_hdw_get_ctrl_v4l(). */
+#define V4L2_CID_PVR_SRATE          (V4L2_CID_PRIVATE_BASE)
+#define V4L2_CID_PVR_AUDIOBITRATE   (V4L2_CID_PRIVATE_BASE+1)
+#define V4L2_CID_PVR_AUDIOCRC       (V4L2_CID_PRIVATE_BASE+2)
+#define V4L2_CID_PVR_AUDIOEMPHASIS  (V4L2_CID_PRIVATE_BASE+3)
+#define V4L2_CID_PVR_VBR            (V4L2_CID_PRIVATE_BASE+4)
+#define V4L2_CID_PVR_VIDEOBITRATE   (V4L2_CID_PRIVATE_BASE+5)
+#define V4L2_CID_PVR_VIDEOPEAK      (V4L2_CID_PRIVATE_BASE+6)
+#define V4L2_CID_PVR_VIDEOSTANDARD  (V4L2_CID_PRIVATE_BASE+7)
+
+/* Private internal control ids, look these up with
+   pvr2_hdw_get_ctrl_by_id() - these are NOT visible in V4L */
+#define PVR2_CID_STDENUM 1
+#define PVR2_CID_STDCUR 2
+#define PVR2_CID_STDAVAIL 3
+#define PVR2_CID_INPUT 4
+#define PVR2_CID_AUDIOMODE 5
+#define PVR2_CID_FREQUENCY 6
+#define PVR2_CID_HRES 7
+#define PVR2_CID_VRES 8
+#define PVR2_CID_INTERLACE 9
+
+/* Legal values for the INPUT state variable */
+#define PVR2_CVAL_INPUT_TV 0
+#define PVR2_CVAL_INPUT_SVIDEO 1
+#define PVR2_CVAL_INPUT_COMPOSITE 2
+#define PVR2_CVAL_INPUT_RADIO 3
+
+/* Values that pvr2_hdw_get_signal_status() returns */
+#define PVR2_SIGNAL_OK     0x0001
+#define PVR2_SIGNAL_STEREO 0x0002
+#define PVR2_SIGNAL_SAP    0x0004
+
+
+/* Subsystem definitions - these are various pieces that can be
+   independently stopped / started.  Usually you don't want to mess with
+   this directly (let the driver handle things itself), but it is useful
+   for debugging. */
+#define PVR2_SUBSYS_B_ENC_FIRMWARE        0
+#define PVR2_SUBSYS_B_ENC_CFG             1
+#define PVR2_SUBSYS_B_DIGITIZER_RUN       2
+#define PVR2_SUBSYS_B_USBSTREAM_RUN       3
+#define PVR2_SUBSYS_B_ENC_RUN             4
+
+#define PVR2_SUBSYS_CFG_ALL ( \
+       (1 << PVR2_SUBSYS_B_ENC_FIRMWARE) | \
+       (1 << PVR2_SUBSYS_B_ENC_CFG) )
+#define PVR2_SUBSYS_RUN_ALL ( \
+       (1 << PVR2_SUBSYS_B_DIGITIZER_RUN) | \
+       (1 << PVR2_SUBSYS_B_USBSTREAM_RUN) | \
+       (1 << PVR2_SUBSYS_B_ENC_RUN) )
+#define PVR2_SUBSYS_ALL ( \
+       PVR2_SUBSYS_CFG_ALL | \
+       PVR2_SUBSYS_RUN_ALL )
+
+enum pvr2_config {
+       pvr2_config_empty,
+       pvr2_config_mpeg,
+       pvr2_config_vbi,
+       pvr2_config_radio,
+};
+
+const char *pvr2_config_get_name(enum pvr2_config);
+
+struct pvr2_hdw;
+
+/* Create and return a structure for interacting with the underlying
+   hardware */
+struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf,
+                                const struct usb_device_id *devid);
+
+/* Poll for background activity (if any) */
+void pvr2_hdw_poll(struct pvr2_hdw *);
+
+/* Trigger a poll to take place later at a convenient time */
+void pvr2_hdw_poll_trigger(struct pvr2_hdw *);
+void pvr2_hdw_poll_trigger_unlocked(struct pvr2_hdw *);
+
+/* Register a callback used to trigger a future poll */
+void pvr2_hdw_setup_poll_trigger(struct pvr2_hdw *,
+                                void (*func)(void *),
+                                void *data);
+
+/* Get pointer to structure given unit number */
+struct pvr2_hdw *pvr2_hdw_find(int unit_number);
+
+/* Destroy hardware interaction structure */
+void pvr2_hdw_destroy(struct pvr2_hdw *);
+
+/* Set up the structure and attempt to put the device into a usable state.
+   This can be a time-consuming operation, which is why it is not done
+   internally as part of the create() step.  Return value is exactly the
+   same as pvr2_hdw_init_ok(). */
+int pvr2_hdw_setup(struct pvr2_hdw *);
+
+/* Initialization succeeded */
+int pvr2_hdw_init_ok(struct pvr2_hdw *);
+
+/* Return true if in the ready (normal) state */
+int pvr2_hdw_dev_ok(struct pvr2_hdw *);
+
+/* Return small integer number [1..N] for logical instance number of this
+   device.  This is useful for indexing array-valued module parameters. */
+int pvr2_hdw_get_unit_number(struct pvr2_hdw *);
+
+/* Get pointer to underlying USB device */
+struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *);
+
+/* Retrieve serial number of device */
+unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *);
+
+/* Called when hardware has been unplugged */
+void pvr2_hdw_disconnect(struct pvr2_hdw *);
+
+/* Get the number of defined controls */
+unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *);
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *,unsigned int);
+
+/* Retrieve a control handle given its internal ID (if any) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *,unsigned int);
+
+/* Retrieve a control handle given its V4L ID (if any) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *,unsigned int ctl_id);
+
+/* Commit all control changes made up to this point */
+int pvr2_hdw_commit_ctl(struct pvr2_hdw *);
+
+/* Return name for this driver instance */
+const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *);
+
+/* Return PVR2_SIGNAL_XXXX bit mask indicating signal status */
+unsigned int pvr2_hdw_get_signal_status(struct pvr2_hdw *);
+
+/* Query device and see if it thinks it is on a high-speed USB link */
+int pvr2_hdw_is_hsm(struct pvr2_hdw *);
+
+/* Turn streaming on/off */
+int pvr2_hdw_set_streaming(struct pvr2_hdw *,int);
+
+/* Find out if streaming is on */
+int pvr2_hdw_get_streaming(struct pvr2_hdw *);
+
+/* Configure the type of stream to generate */
+int pvr2_hdw_set_stream_type(struct pvr2_hdw *, enum pvr2_config);
+
+/* Get handle to video output stream */
+struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *);
+
+/* Emit a video standard struct */
+int pvr2_hdw_get_stdenum_value(struct pvr2_hdw *hdw,struct v4l2_standard *std,
+                              unsigned int idx);
+
+/* Enable / disable various pieces of hardware.  Items to change are
+   identified by bit positions within msk, and new state for each item is
+   identified by corresponding bit positions within val. */
+void pvr2_hdw_subsys_bit_chg(struct pvr2_hdw *hdw,
+                            unsigned long msk,unsigned long val);
+
+/* Shortcut for pvr2_hdw_subsys_bit_chg(hdw,msk,msk) */
+void pvr2_hdw_subsys_bit_set(struct pvr2_hdw *hdw,unsigned long msk);
+
+/* Shortcut for pvr2_hdw_subsys_bit_chg(hdw,msk,0) */
+void pvr2_hdw_subsys_bit_clr(struct pvr2_hdw *hdw,unsigned long msk);
+
+/* Retrieve mask indicating which pieces of hardware are currently enabled
+   / configured. */
+unsigned long pvr2_hdw_subsys_get(struct pvr2_hdw *);
+
+/* Adjust mask of what get shut down when streaming is stopped.  This is a
+   debugging aid. */
+void pvr2_hdw_subsys_stream_bit_chg(struct pvr2_hdw *hdw,
+                                   unsigned long msk,unsigned long val);
+
+/* Retrieve mask indicating which pieces of hardware are disabled when
+   streaming is turned off. */
+unsigned long pvr2_hdw_subsys_stream_get(struct pvr2_hdw *);
+
+
+/* Enable / disable retrieval of CPU firmware.  This must be enabled before
+   pvr2_hdw_cpufw_get() will function.  Note that doing this may prevent
+   the device from running (and leaving this mode may imply a device
+   reset). */
+void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *, int enable_flag);
+
+/* Return true if we're in a mode for retrieval CPU firmware */
+int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *);
+
+/* Retrieve a piece of the CPU's firmware at the given offset.  Return
+   value is the number of bytes retrieved or zero if we're past the end or
+   an error otherwise (e.g. if firmware retrieval is not enabled). */
+int pvr2_hdw_cpufw_get(struct pvr2_hdw *,unsigned int offs,
+                      char *buf,unsigned int cnt);
+
+/* Retrieve previously stored v4l minor device number */
+int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *);
+
+/* Store the v4l minor device number */
+void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *,int);
+
+
+/* The following entry points are all lower level things you normally don't
+   want to worry about. */
+
+/* Attempt to recover from a USB foul-up (in practice I find that if you
+   have to do this, then it's already too late). */
+void pvr2_reset_ctl_endpoints(struct pvr2_hdw *hdw);
+
+/* Issue a command and get a response from the device.  LOTS of higher
+   level stuff is built on this. */
+int pvr2_send_request(struct pvr2_hdw *,
+                     void *write_ptr,unsigned int write_len,
+                     void *read_ptr,unsigned int read_len);
+
+/* Issue a command and get a response from the device.  This extended
+   version includes a probe flag (which if set means that device errors
+   should not be logged or treated as fatal) and a timeout in jiffies.
+   This can be used to non-lethally probe the health of endpoint 1. */
+int pvr2_send_request_ex(struct pvr2_hdw *,unsigned int timeout,int probe_fl,
+                        void *write_ptr,unsigned int write_len,
+                        void *read_ptr,unsigned int read_len);
+
+/* Slightly higher level device communication functions. */
+int pvr2_write_register(struct pvr2_hdw *, u16, u32);
+int pvr2_read_register(struct pvr2_hdw *, u16, u32 *);
+int pvr2_write_u16(struct pvr2_hdw *, u16, int);
+int pvr2_write_u8(struct pvr2_hdw *, u8, int);
+
+/* Call if for any reason we can't talk to the hardware anymore - this will
+   cause the driver to stop flailing on the device. */
+void pvr2_hdw_render_useless(struct pvr2_hdw *);
+void pvr2_hdw_render_useless_unlocked(struct pvr2_hdw *);
+
+/* Set / clear 8051's reset bit */
+void pvr2_hdw_cpureset_assert(struct pvr2_hdw *,int);
+
+/* Execute a USB-commanded device reset */
+void pvr2_hdw_device_reset(struct pvr2_hdw *);
+
+/* Execute hard reset command (after this point it's likely that the
+   encoder will have to be reconfigured).  This also clears the "useless"
+   state. */
+int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *);
+
+/* Execute simple reset command */
+int pvr2_hdw_cmd_powerup(struct pvr2_hdw *);
+
+/* Order decoder to reset */
+int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *);
+
+/* Stop / start video stream transport */
+int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl);
+
+/* Find I2C address of eeprom */
+int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *);
+
+/* Direct manipulation of GPIO bits */
+int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val);
+int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val);
+
+/* This data structure is specifically for the next function... */
+struct pvr2_hdw_debug_info {
+       int big_lock_held;
+       int ctl_lock_held;
+       int flag_ok;
+       int flag_disconnected;
+       int flag_init_ok;
+       int flag_streaming_enabled;
+       unsigned long subsys_flags;
+       int cmd_debug_state;
+       int cmd_debug_write_len;
+       int cmd_debug_read_len;
+       int cmd_debug_write_pend;
+       int cmd_debug_read_pend;
+       int cmd_debug_timeout;
+       int cmd_debug_rstatus;
+       int cmd_debug_wstatus;
+       unsigned char cmd_code;
+};
+
+/* Non-intrusively retrieve internal state info - this is useful for
+   diagnosing lockups.  Note that this operation is completed without any
+   kind of locking and so it is not atomic and may yield inconsistent
+   results.  This is *purely* a debugging aid. */
+void pvr2_hdw_get_debug_info(const struct pvr2_hdw *hdw,
+                            struct pvr2_hdw_debug_info *);
+
+/* Cause modules to log their state once */
+void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw);
+
+/* Cause encoder firmware to be uploaded into the device.  This is normally
+   done autonomously, but the interface is exported here because it is also
+   a debugging aid. */
+int pvr2_upload_firmware2(struct pvr2_hdw *hdw);
+
+/* List of device types that we can match */
+extern struct usb_device_id pvr2_device_table[];
+
+#endif /* __PVRUSB2_HDW_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c
new file mode 100644 (file)
index 0000000..1dd4f62
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ *
+ *  $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"
+#include "pvrusb2-i2c-cmd-v4l2.h"
+#include "pvrusb2-audio.h"
+#include "pvrusb2-tuner.h"
+#include "pvrusb2-demod.h"
+#include "pvrusb2-video-v4l.h"
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+#include "pvrusb2-cx2584x-v4l.h"
+#include "pvrusb2-wm8775.h"
+#endif
+
+#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__)
+
+#define OP_STANDARD 0
+#define OP_BCSH 1
+#define OP_VOLUME 2
+#define OP_FREQ 3
+#define OP_AUDIORATE 4
+#define OP_SIZE 5
+#define OP_LOG 6
+
+static const struct pvr2_i2c_op * const ops[] = {
+       [OP_STANDARD] = &pvr2_i2c_op_v4l2_standard,
+       [OP_BCSH] = &pvr2_i2c_op_v4l2_bcsh,
+       [OP_VOLUME] = &pvr2_i2c_op_v4l2_volume,
+       [OP_FREQ] = &pvr2_i2c_op_v4l2_frequency,
+       [OP_SIZE] = &pvr2_i2c_op_v4l2_size,
+       [OP_LOG] = &pvr2_i2c_op_v4l2_log,
+};
+
+void pvr2_i2c_probe(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp)
+{
+       int id;
+       id = cp->client->driver->id;
+       cp->ctl_mask = ((1 << OP_STANDARD) |
+                       (1 << OP_BCSH) |
+                       (1 << OP_VOLUME) |
+                       (1 << OP_FREQ) |
+                       (1 << OP_SIZE) |
+                       (1 << OP_LOG));
+
+       if (id == I2C_DRIVERID_MSP3400) {
+               if (pvr2_i2c_msp3400_setup(hdw,cp)) {
+                       return;
+               }
+       }
+       if (id == I2C_DRIVERID_TUNER) {
+               if (pvr2_i2c_tuner_setup(hdw,cp)) {
+                       return;
+               }
+       }
+#ifdef CONFIG_VIDEO_PVRUSB2_24XXX
+       if (id == I2C_DRIVERID_CX25840) {
+               if (pvr2_i2c_cx2584x_v4l_setup(hdw,cp)) {
+                       return;
+               }
+       }
+       if (id == I2C_DRIVERID_WM8775) {
+               if (pvr2_i2c_wm8775_setup(hdw,cp)) {
+                       return;
+               }
+       }
+#endif
+       if (id == I2C_DRIVERID_SAA711X) {
+               if (pvr2_i2c_decoder_v4l_setup(hdw,cp)) {
+                       return;
+               }
+       }
+       if (id == I2C_DRIVERID_TDA9887) {
+               if (pvr2_i2c_demod_setup(hdw,cp)) {
+                       return;
+               }
+       }
+}
+
+
+const struct pvr2_i2c_op *pvr2_i2c_get_op(unsigned int idx)
+{
+       if (idx >= sizeof(ops)/sizeof(ops[0])) return 0;
+       return ops[idx];
+}
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c
new file mode 100644 (file)
index 0000000..9f81aff
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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-cmd-v4l2.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+
+
+static void set_standard(struct pvr2_hdw *hdw)
+{
+       v4l2_std_id vs;
+       vs = hdw->std_mask_cur;
+       pvr2_trace(PVR2_TRACE_CHIPS,
+                  "i2c v4l2 set_standard(0x%llx)",(__u64)vs);
+
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_STD,&vs);
+}
+
+
+static int check_standard(struct pvr2_hdw *hdw)
+{
+       return hdw->std_dirty != 0;
+}
+
+
+const struct pvr2_i2c_op pvr2_i2c_op_v4l2_standard = {
+       .check = check_standard,
+       .update = set_standard,
+       .name = "v4l2_standard",
+};
+
+
+static void set_bcsh(struct pvr2_hdw *hdw)
+{
+       struct v4l2_control ctrl;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_bcsh"
+                  " b=%d c=%d s=%d h=%d",
+                  hdw->brightness_val,hdw->contrast_val,
+                  hdw->saturation_val,hdw->hue_val);
+       memset(&ctrl,0,sizeof(ctrl));
+       ctrl.id = V4L2_CID_BRIGHTNESS;
+       ctrl.value = hdw->brightness_val;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+       ctrl.id = V4L2_CID_CONTRAST;
+       ctrl.value = hdw->contrast_val;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+       ctrl.id = V4L2_CID_SATURATION;
+       ctrl.value = hdw->saturation_val;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+       ctrl.id = V4L2_CID_HUE;
+       ctrl.value = hdw->hue_val;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+}
+
+
+static int check_bcsh(struct pvr2_hdw *hdw)
+{
+       return (hdw->brightness_dirty ||
+               hdw->contrast_dirty ||
+               hdw->saturation_dirty ||
+               hdw->hue_dirty);
+}
+
+
+const struct pvr2_i2c_op pvr2_i2c_op_v4l2_bcsh = {
+       .check = check_bcsh,
+       .update = set_bcsh,
+       .name = "v4l2_bcsh",
+};
+
+
+static void set_volume(struct pvr2_hdw *hdw)
+{
+       struct v4l2_control ctrl;
+       pvr2_trace(PVR2_TRACE_CHIPS,
+                  "i2c v4l2 set_volume"
+                  "(vol=%d bal=%d bas=%d treb=%d mute=%d)",
+                  hdw->volume_val,
+                  hdw->balance_val,
+                  hdw->bass_val,
+                  hdw->treble_val,
+                  hdw->mute_val);
+       memset(&ctrl,0,sizeof(ctrl));
+       ctrl.id = V4L2_CID_AUDIO_MUTE;
+       ctrl.value = hdw->mute_val ? 1 : 0;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+       ctrl.id = V4L2_CID_AUDIO_VOLUME;
+       ctrl.value = hdw->volume_val;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+       ctrl.id = V4L2_CID_AUDIO_BALANCE;
+       ctrl.value = hdw->balance_val;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+       ctrl.id = V4L2_CID_AUDIO_BASS;
+       ctrl.value = hdw->bass_val;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+       ctrl.id = V4L2_CID_AUDIO_TREBLE;
+       ctrl.value = hdw->treble_val;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl);
+}
+
+
+static int check_volume(struct pvr2_hdw *hdw)
+{
+       return (hdw->volume_dirty ||
+               hdw->balance_dirty ||
+               hdw->bass_dirty ||
+               hdw->treble_dirty ||
+               hdw->mute_dirty);
+}
+
+
+const struct pvr2_i2c_op pvr2_i2c_op_v4l2_volume = {
+       .check = check_volume,
+       .update = set_volume,
+       .name = "v4l2_volume",
+};
+
+
+static void set_frequency(struct pvr2_hdw *hdw)
+{
+       unsigned long fv;
+       struct v4l2_frequency freq;
+       fv = hdw->freqVal;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_freq(%lu)",fv);
+       memset(&freq,0,sizeof(freq));
+       freq.frequency = fv / 62500;
+       freq.tuner = 0;
+       freq.type = V4L2_TUNER_ANALOG_TV;
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_FREQUENCY,&freq);
+}
+
+
+static int check_frequency(struct pvr2_hdw *hdw)
+{
+       return hdw->freqDirty != 0;
+}
+
+
+const struct pvr2_i2c_op pvr2_i2c_op_v4l2_frequency = {
+       .check = check_frequency,
+       .update = set_frequency,
+       .name = "v4l2_freq",
+};
+
+
+static void set_size(struct pvr2_hdw *hdw)
+{
+       struct v4l2_format fmt;
+
+       memset(&fmt,0,sizeof(fmt));
+
+       fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       fmt.fmt.pix.width = hdw->res_hor_val;
+       fmt.fmt.pix.height = hdw->res_ver_val;
+
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_size(%dx%d)",
+                          fmt.fmt.pix.width,fmt.fmt.pix.height);
+
+       pvr2_i2c_core_cmd(hdw,VIDIOC_S_FMT,&fmt);
+}
+
+
+static int check_size(struct pvr2_hdw *hdw)
+{
+       return (hdw->res_hor_dirty || hdw->res_ver_dirty);
+}
+
+
+const struct pvr2_i2c_op pvr2_i2c_op_v4l2_size = {
+       .check = check_size,
+       .update = set_size,
+       .name = "v4l2_size",
+};
+
+
+static void do_log(struct pvr2_hdw *hdw)
+{
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 do_log()");
+       pvr2_i2c_core_cmd(hdw,VIDIOC_LOG_STATUS,0);
+
+}
+
+
+static int check_log(struct pvr2_hdw *hdw)
+{
+       return hdw->log_requested != 0;
+}
+
+
+const struct pvr2_i2c_op pvr2_i2c_op_v4l2_log = {
+       .check = check_log,
+       .update = do_log,
+       .name = "v4l2_log",
+};
+
+
+void pvr2_v4l2_cmd_stream(struct pvr2_i2c_client *cp,int fl)
+{
+       pvr2_i2c_client_cmd(cp,
+                           (fl ? VIDIOC_STREAMON : VIDIOC_STREAMOFF),0);
+}
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h
new file mode 100644 (file)
index 0000000..ecabddb
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+#ifndef __PVRUSB2_CMD_V4L2_H
+#define __PVRUSB2_CMD_V4L2_H
+
+#include "pvrusb2-i2c-core.h"
+
+extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_standard;
+extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_bcsh;
+extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_volume;
+extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_frequency;
+extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_size;
+extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_log;
+
+void pvr2_v4l2_cmd_stream(struct pvr2_i2c_client *,int);
+
+#endif /* __PVRUSB2_CMD_V4L2_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
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..c8d0bde
--- /dev/null
@@ -0,0 +1,937 @@
+/*
+ *
+ *  $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 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. */
+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,
+                        struct i2c_msg msgs[],
+                        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) {
+                       /* Simple read */
+                       u16 tcnt,bcnt,offs;
+                       if (!msgs[0].len) {
+                               /* Length == 0 read.  This is a probe. */
+                               if (funcp(hdw,msgs[0].addr,0,0,0,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,0,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,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;
+                       /* 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;
+}
+
+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 = 0;
+       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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h b/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h
new file mode 100644 (file)
index 0000000..e8af5b0
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_I2C_CORE_H
+#define __PVRUSB2_I2C_CORE_H
+
+#include <linux/list.h>
+#include <linux/i2c.h>
+
+struct pvr2_hdw;
+struct pvr2_i2c_client;
+struct pvr2_i2c_handler;
+struct pvr2_i2c_handler_functions;
+struct pvr2_i2c_op;
+struct pvr2_i2c_op_functions;
+
+struct pvr2_i2c_client {
+       struct i2c_client *client;
+       struct pvr2_i2c_handler *handler;
+       struct list_head list;
+       int detected_flag;
+       int recv_enable;
+       unsigned long pend_mask;
+       unsigned long ctl_mask;
+};
+
+struct pvr2_i2c_handler {
+       void *func_data;
+       const struct pvr2_i2c_handler_functions *func_table;
+};
+
+struct pvr2_i2c_handler_functions {
+       void (*detach)(void *);
+       int (*check)(void *);
+       void (*update)(void *);
+       unsigned int (*describe)(void *,char *,unsigned int);
+};
+
+struct pvr2_i2c_op {
+       int (*check)(struct pvr2_hdw *);
+       void (*update)(struct pvr2_hdw *);
+       const char *name;
+};
+
+void pvr2_i2c_core_init(struct pvr2_hdw *);
+void pvr2_i2c_core_done(struct pvr2_hdw *);
+
+int pvr2_i2c_client_cmd(struct pvr2_i2c_client *,unsigned int cmd,void *arg);
+int pvr2_i2c_core_cmd(struct pvr2_hdw *,unsigned int cmd,void *arg);
+
+int pvr2_i2c_core_check_stale(struct pvr2_hdw *);
+void pvr2_i2c_core_sync(struct pvr2_hdw *);
+unsigned int pvr2_i2c_report(struct pvr2_hdw *,char *buf,unsigned int maxlen);
+#define PVR2_I2C_DETAIL_DEBUG   0x0001
+#define PVR2_I2C_DETAIL_HANDLER 0x0002
+#define PVR2_I2C_DETAIL_CTLMASK 0x0004
+#define PVR2_I2C_DETAIL_ALL (\
+       PVR2_I2C_DETAIL_DEBUG |\
+       PVR2_I2C_DETAIL_HANDLER |\
+       PVR2_I2C_DETAIL_CTLMASK)
+unsigned int pvr2_i2c_client_describe(struct pvr2_i2c_client *,
+                                     unsigned int detail_mask,
+                                     char *buf,unsigned int maxlen);
+
+void pvr2_i2c_probe(struct pvr2_hdw *,struct pvr2_i2c_client *);
+const struct pvr2_i2c_op *pvr2_i2c_get_op(unsigned int idx);
+
+#endif /* __PVRUSB2_I2C_CORE_H */
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-io.c b/drivers/media/video/pvrusb2/pvrusb2-io.c
new file mode 100644 (file)
index 0000000..a984c91
--- /dev/null
@@ -0,0 +1,695 @@
+/*
+ *
+ *  $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-io.h"
+#include "pvrusb2-debug.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#define BUFFER_SIG 0x47653271
+
+// #define SANITY_CHECK_BUFFERS
+
+
+#ifdef SANITY_CHECK_BUFFERS
+#define BUFFER_CHECK(bp) do { \
+       if ((bp)->signature != BUFFER_SIG) { \
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS, \
+               "Buffer %p is bad at %s:%d", \
+               (bp),__FILE__,__LINE__); \
+               pvr2_buffer_describe(bp,"BadSig"); \
+               BUG(); \
+       } \
+} while (0)
+#else
+#define BUFFER_CHECK(bp) do {} while(0)
+#endif
+
+struct pvr2_stream {
+       /* Buffers queued for reading */
+       struct list_head queued_list;
+       unsigned int q_count;
+       unsigned int q_bcount;
+       /* Buffers with retrieved data */
+       struct list_head ready_list;
+       unsigned int r_count;
+       unsigned int r_bcount;
+       /* Buffers available for use */
+       struct list_head idle_list;
+       unsigned int i_count;
+       unsigned int i_bcount;
+       /* Pointers to all buffers */
+       struct pvr2_buffer **buffers;
+       /* Array size of buffers */
+       unsigned int buffer_slot_count;
+       /* Total buffers actually in circulation */
+       unsigned int buffer_total_count;
+       /* Designed number of buffers to be in circulation */
+       unsigned int buffer_target_count;
+       /* Executed when ready list become non-empty */
+       pvr2_stream_callback callback_func;
+       void *callback_data;
+       /* Context for transfer endpoint */
+       struct usb_device *dev;
+       int endpoint;
+       /* Overhead for mutex enforcement */
+       spinlock_t list_lock;
+       struct mutex mutex;
+       /* Tracking state for tolerating errors */
+       unsigned int fail_count;
+       unsigned int fail_tolerance;
+};
+
+struct pvr2_buffer {
+       int id;
+       int signature;
+       enum pvr2_buffer_state state;
+       void *ptr;               /* Pointer to storage area */
+       unsigned int max_count;  /* Size of storage area */
+       unsigned int used_count; /* Amount of valid data in storage area */
+       int status;              /* Transfer result status */
+       struct pvr2_stream *stream;
+       struct list_head list_overhead;
+       struct urb *purb;
+};
+
+const char *pvr2_buffer_state_decode(enum pvr2_buffer_state st)
+{
+       switch (st) {
+       case pvr2_buffer_state_none: return "none";
+       case pvr2_buffer_state_idle: return "idle";
+       case pvr2_buffer_state_queued: return "queued";
+       case pvr2_buffer_state_ready: return "ready";
+       }
+       return "unknown";
+}
+
+void pvr2_buffer_describe(struct pvr2_buffer *bp,const char *msg)
+{
+       pvr2_trace(PVR2_TRACE_INFO,
+                  "buffer%s%s %p state=%s id=%d status=%d"
+                  " stream=%p purb=%p sig=0x%x",
+                  (msg ? " " : ""),
+                  (msg ? msg : ""),
+                  bp,
+                  (bp ? pvr2_buffer_state_decode(bp->state) : "(invalid)"),
+                  (bp ? bp->id : 0),
+                  (bp ? bp->status : 0),
+                  (bp ? bp->stream : 0),
+                  (bp ? bp->purb : 0),
+                  (bp ? bp->signature : 0));
+}
+
+static void pvr2_buffer_remove(struct pvr2_buffer *bp)
+{
+       unsigned int *cnt;
+       unsigned int *bcnt;
+       unsigned int ccnt;
+       struct pvr2_stream *sp = bp->stream;
+       switch (bp->state) {
+       case pvr2_buffer_state_idle:
+               cnt = &sp->i_count;
+               bcnt = &sp->i_bcount;
+               ccnt = bp->max_count;
+               break;
+       case pvr2_buffer_state_queued:
+               cnt = &sp->q_count;
+               bcnt = &sp->q_bcount;
+               ccnt = bp->max_count;
+               break;
+       case pvr2_buffer_state_ready:
+               cnt = &sp->r_count;
+               bcnt = &sp->r_bcount;
+               ccnt = bp->used_count;
+               break;
+       default:
+               return;
+       }
+       list_del_init(&bp->list_overhead);
+       (*cnt)--;
+       (*bcnt) -= ccnt;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/"
+                  " bufferPool     %8s dec cap=%07d cnt=%02d",
+                  pvr2_buffer_state_decode(bp->state),*bcnt,*cnt);
+       bp->state = pvr2_buffer_state_none;
+}
+
+static void pvr2_buffer_set_none(struct pvr2_buffer *bp)
+{
+       unsigned long irq_flags;
+       struct pvr2_stream *sp;
+       BUFFER_CHECK(bp);
+       sp = bp->stream;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+                  bp,
+                  pvr2_buffer_state_decode(bp->state),
+                  pvr2_buffer_state_decode(pvr2_buffer_state_none));
+       spin_lock_irqsave(&sp->list_lock,irq_flags);
+       pvr2_buffer_remove(bp);
+       spin_unlock_irqrestore(&sp->list_lock,irq_flags);
+}
+
+static int pvr2_buffer_set_ready(struct pvr2_buffer *bp)
+{
+       int fl;
+       unsigned long irq_flags;
+       struct pvr2_stream *sp;
+       BUFFER_CHECK(bp);
+       sp = bp->stream;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+                  bp,
+                  pvr2_buffer_state_decode(bp->state),
+                  pvr2_buffer_state_decode(pvr2_buffer_state_ready));
+       spin_lock_irqsave(&sp->list_lock,irq_flags);
+       fl = (sp->r_count == 0);
+       pvr2_buffer_remove(bp);
+       list_add_tail(&bp->list_overhead,&sp->ready_list);
+       bp->state = pvr2_buffer_state_ready;
+       (sp->r_count)++;
+       sp->r_bcount += bp->used_count;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/"
+                  " bufferPool     %8s inc cap=%07d cnt=%02d",
+                  pvr2_buffer_state_decode(bp->state),
+                  sp->r_bcount,sp->r_count);
+       spin_unlock_irqrestore(&sp->list_lock,irq_flags);
+       return fl;
+}
+
+static void pvr2_buffer_set_idle(struct pvr2_buffer *bp)
+{
+       unsigned long irq_flags;
+       struct pvr2_stream *sp;
+       BUFFER_CHECK(bp);
+       sp = bp->stream;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+                  bp,
+                  pvr2_buffer_state_decode(bp->state),
+                  pvr2_buffer_state_decode(pvr2_buffer_state_idle));
+       spin_lock_irqsave(&sp->list_lock,irq_flags);
+       pvr2_buffer_remove(bp);
+       list_add_tail(&bp->list_overhead,&sp->idle_list);
+       bp->state = pvr2_buffer_state_idle;
+       (sp->i_count)++;
+       sp->i_bcount += bp->max_count;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/"
+                  " bufferPool     %8s inc cap=%07d cnt=%02d",
+                  pvr2_buffer_state_decode(bp->state),
+                  sp->i_bcount,sp->i_count);
+       spin_unlock_irqrestore(&sp->list_lock,irq_flags);
+}
+
+static void pvr2_buffer_set_queued(struct pvr2_buffer *bp)
+{
+       unsigned long irq_flags;
+       struct pvr2_stream *sp;
+       BUFFER_CHECK(bp);
+       sp = bp->stream;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+                  bp,
+                  pvr2_buffer_state_decode(bp->state),
+                  pvr2_buffer_state_decode(pvr2_buffer_state_queued));
+       spin_lock_irqsave(&sp->list_lock,irq_flags);
+       pvr2_buffer_remove(bp);
+       list_add_tail(&bp->list_overhead,&sp->queued_list);
+       bp->state = pvr2_buffer_state_queued;
+       (sp->q_count)++;
+       sp->q_bcount += bp->max_count;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/"
+                  " bufferPool     %8s inc cap=%07d cnt=%02d",
+                  pvr2_buffer_state_decode(bp->state),
+                  sp->q_bcount,sp->q_count);
+       spin_unlock_irqrestore(&sp->list_lock,irq_flags);
+}
+
+static void pvr2_buffer_wipe(struct pvr2_buffer *bp)
+{
+       if (bp->state == pvr2_buffer_state_queued) {
+               usb_kill_urb(bp->purb);
+       }
+}
+
+static int pvr2_buffer_init(struct pvr2_buffer *bp,
+                           struct pvr2_stream *sp,
+                           unsigned int id)
+{
+       memset(bp,0,sizeof(*bp));
+       bp->signature = BUFFER_SIG;
+       bp->id = id;
+       pvr2_trace(PVR2_TRACE_BUF_POOL,
+                  "/*---TRACE_FLOW---*/ bufferInit     %p stream=%p",bp,sp);
+       bp->stream = sp;
+       bp->state = pvr2_buffer_state_none;
+       INIT_LIST_HEAD(&bp->list_overhead);
+       bp->purb = usb_alloc_urb(0,GFP_KERNEL);
+       if (! bp->purb) return -ENOMEM;
+#ifdef SANITY_CHECK_BUFFERS
+       pvr2_buffer_describe(bp,"create");
+#endif
+       return 0;
+}
+
+static void pvr2_buffer_done(struct pvr2_buffer *bp)
+{
+#ifdef SANITY_CHECK_BUFFERS
+       pvr2_buffer_describe(bp,"delete");
+#endif
+       pvr2_buffer_wipe(bp);
+       pvr2_buffer_set_none(bp);
+       bp->signature = 0;
+       bp->stream = 0;
+       if (bp->purb) usb_free_urb(bp->purb);
+       pvr2_trace(PVR2_TRACE_BUF_POOL,"/*---TRACE_FLOW---*/"
+                  " bufferDone     %p",bp);
+}
+
+static int pvr2_stream_buffer_count(struct pvr2_stream *sp,unsigned int cnt)
+{
+       int ret;
+       unsigned int scnt;
+
+       /* Allocate buffers pointer array in multiples of 32 entries */
+       if (cnt == sp->buffer_total_count) return 0;
+
+       pvr2_trace(PVR2_TRACE_BUF_POOL,
+                  "/*---TRACE_FLOW---*/ poolResize    "
+                  " stream=%p cur=%d adj=%+d",
+                  sp,
+                  sp->buffer_total_count,
+                  cnt-sp->buffer_total_count);
+
+       scnt = cnt & ~0x1f;
+       if (cnt > scnt) scnt += 0x20;
+
+       if (cnt > sp->buffer_total_count) {
+               if (scnt > sp->buffer_slot_count) {
+                       struct pvr2_buffer **nb;
+                       nb = kmalloc(scnt * sizeof(*nb),GFP_KERNEL);
+                       if (!nb) return -ENOMEM;
+                       if (sp->buffer_slot_count) {
+                               memcpy(nb,sp->buffers,
+                                      sp->buffer_slot_count * sizeof(*nb));
+                               kfree(sp->buffers);
+                       }
+                       sp->buffers = nb;
+                       sp->buffer_slot_count = scnt;
+               }
+               while (sp->buffer_total_count < cnt) {
+                       struct pvr2_buffer *bp;
+                       bp = kmalloc(sizeof(*bp),GFP_KERNEL);
+                       if (!bp) return -ENOMEM;
+                       ret = pvr2_buffer_init(bp,sp,sp->buffer_total_count);
+                       if (ret) {
+                               kfree(bp);
+                               return -ENOMEM;
+                       }
+                       sp->buffers[sp->buffer_total_count] = bp;
+                       (sp->buffer_total_count)++;
+                       pvr2_buffer_set_idle(bp);
+               }
+       } else {
+               while (sp->buffer_total_count > cnt) {
+                       struct pvr2_buffer *bp;
+                       bp = sp->buffers[sp->buffer_total_count - 1];
+                       /* Paranoia */
+                       sp->buffers[sp->buffer_total_count - 1] = 0;
+                       (sp->buffer_total_count)--;
+                       pvr2_buffer_done(bp);
+                       kfree(bp);
+               }
+               if (scnt < sp->buffer_slot_count) {
+                       struct pvr2_buffer **nb = 0;
+                       if (scnt) {
+                               nb = kmalloc(scnt * sizeof(*nb),GFP_KERNEL);
+                               if (!nb) return -ENOMEM;
+                               memcpy(nb,sp->buffers,scnt * sizeof(*nb));
+                       }
+                       kfree(sp->buffers);
+                       sp->buffers = nb;
+                       sp->buffer_slot_count = scnt;
+               }
+       }
+       return 0;
+}
+
+static int pvr2_stream_achieve_buffer_count(struct pvr2_stream *sp)
+{
+       struct pvr2_buffer *bp;
+       unsigned int cnt;
+
+       if (sp->buffer_total_count == sp->buffer_target_count) return 0;
+
+       pvr2_trace(PVR2_TRACE_BUF_POOL,
+                  "/*---TRACE_FLOW---*/"
+                  " poolCheck      stream=%p cur=%d tgt=%d",
+                  sp,sp->buffer_total_count,sp->buffer_target_count);
+
+       if (sp->buffer_total_count < sp->buffer_target_count) {
+               return pvr2_stream_buffer_count(sp,sp->buffer_target_count);
+       }
+
+       cnt = 0;
+       while ((sp->buffer_total_count - cnt) > sp->buffer_target_count) {
+               bp = sp->buffers[sp->buffer_total_count - (cnt + 1)];
+               if (bp->state != pvr2_buffer_state_idle) break;
+               cnt++;
+       }
+       if (cnt) {
+               pvr2_stream_buffer_count(sp,sp->buffer_total_count - cnt);
+       }
+
+       return 0;
+}
+
+static void pvr2_stream_internal_flush(struct pvr2_stream *sp)
+{
+       struct list_head *lp;
+       struct pvr2_buffer *bp1;
+       while ((lp = sp->queued_list.next) != &sp->queued_list) {
+               bp1 = list_entry(lp,struct pvr2_buffer,list_overhead);
+               pvr2_buffer_wipe(bp1);
+               /* At this point, we should be guaranteed that no
+                  completion callback may happen on this buffer.  But it's
+                  possible that it might have completed after we noticed
+                  it but before we wiped it.  So double check its status
+                  here first. */
+               if (bp1->state != pvr2_buffer_state_queued) continue;
+               pvr2_buffer_set_idle(bp1);
+       }
+       if (sp->buffer_total_count != sp->buffer_target_count) {
+               pvr2_stream_achieve_buffer_count(sp);
+       }
+}
+
+static void pvr2_stream_init(struct pvr2_stream *sp)
+{
+       spin_lock_init(&sp->list_lock);
+       mutex_init(&sp->mutex);
+       INIT_LIST_HEAD(&sp->queued_list);
+       INIT_LIST_HEAD(&sp->ready_list);
+       INIT_LIST_HEAD(&sp->idle_list);
+}
+
+static void pvr2_stream_done(struct pvr2_stream *sp)
+{
+       mutex_lock(&sp->mutex); do {
+               pvr2_stream_internal_flush(sp);
+               pvr2_stream_buffer_count(sp,0);
+       } while (0); mutex_unlock(&sp->mutex);
+}
+
+static void buffer_complete(struct urb *urb, struct pt_regs *regs)
+{
+       struct pvr2_buffer *bp = urb->context;
+       struct pvr2_stream *sp;
+       unsigned long irq_flags;
+       BUFFER_CHECK(bp);
+       sp = bp->stream;
+       bp->used_count = 0;
+       bp->status = 0;
+       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                  "/*---TRACE_FLOW---*/ bufferComplete %p stat=%d cnt=%d",
+                  bp,urb->status,urb->actual_length);
+       spin_lock_irqsave(&sp->list_lock,irq_flags);
+       if ((!(urb->status)) ||
+           (urb->status == -ENOENT) ||
+           (urb->status == -ECONNRESET) ||
+           (urb->status == -ESHUTDOWN)) {
+               bp->used_count = urb->actual_length;
+               if (sp->fail_count) {
+                       pvr2_trace(PVR2_TRACE_TOLERANCE,
+                                  "stream %p transfer ok"
+                                  " - fail count reset",sp);
+                       sp->fail_count = 0;
+               }
+       } else if (sp->fail_count < sp->fail_tolerance) {
+               // We can tolerate this error, because we're below the
+               // threshold...
+               (sp->fail_count)++;
+               pvr2_trace(PVR2_TRACE_TOLERANCE,
+                          "stream %p ignoring error %d"
+                          " - fail count increased to %u",
+                          sp,urb->status,sp->fail_count);
+       } else {
+               bp->status = urb->status;
+       }
+       spin_unlock_irqrestore(&sp->list_lock,irq_flags);
+       pvr2_buffer_set_ready(bp);
+       if (sp && sp->callback_func) {
+               sp->callback_func(sp->callback_data);
+       }
+}
+
+struct pvr2_stream *pvr2_stream_create(void)
+{
+       struct pvr2_stream *sp;
+       sp = kmalloc(sizeof(*sp),GFP_KERNEL);
+       if (!sp) return sp;
+       memset(sp,0,sizeof(*sp));
+       pvr2_trace(PVR2_TRACE_INIT,"pvr2_stream_create: sp=%p",sp);
+       pvr2_stream_init(sp);
+       return sp;
+}
+
+void pvr2_stream_destroy(struct pvr2_stream *sp)
+{
+       if (!sp) return;
+       pvr2_trace(PVR2_TRACE_INIT,"pvr2_stream_destroy: sp=%p",sp);
+       pvr2_stream_done(sp);
+       kfree(sp);
+}
+
+void pvr2_stream_setup(struct pvr2_stream *sp,
+                      struct usb_device *dev,
+                      int endpoint,
+                      unsigned int tolerance)
+{
+       mutex_lock(&sp->mutex); do {
+               pvr2_stream_internal_flush(sp);
+               sp->dev = dev;
+               sp->endpoint = endpoint;
+               sp->fail_tolerance = tolerance;
+       } while(0); mutex_unlock(&sp->mutex);
+}
+
+void pvr2_stream_set_callback(struct pvr2_stream *sp,
+                             pvr2_stream_callback func,
+                             void *data)
+{
+       unsigned long irq_flags;
+       mutex_lock(&sp->mutex); do {
+               spin_lock_irqsave(&sp->list_lock,irq_flags);
+               sp->callback_data = data;
+               sp->callback_func = func;
+               spin_unlock_irqrestore(&sp->list_lock,irq_flags);
+       } while(0); mutex_unlock(&sp->mutex);
+}
+
+/* Query / set the nominal buffer count */
+int pvr2_stream_get_buffer_count(struct pvr2_stream *sp)
+{
+       return sp->buffer_target_count;
+}
+
+int pvr2_stream_set_buffer_count(struct pvr2_stream *sp,unsigned int cnt)
+{
+       int ret;
+       if (sp->buffer_target_count == cnt) return 0;
+       mutex_lock(&sp->mutex); do {
+               sp->buffer_target_count = cnt;
+               ret = pvr2_stream_achieve_buffer_count(sp);
+       } while(0); mutex_unlock(&sp->mutex);
+       return ret;
+}
+
+struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *sp)
+{
+       struct list_head *lp = sp->idle_list.next;
+       if (lp == &sp->idle_list) return 0;
+       return list_entry(lp,struct pvr2_buffer,list_overhead);
+}
+
+struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *sp)
+{
+       struct list_head *lp = sp->ready_list.next;
+       if (lp == &sp->ready_list) return 0;
+       return list_entry(lp,struct pvr2_buffer,list_overhead);
+}
+
+struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp,int id)
+{
+       if (id < 0) return 0;
+       if (id >= sp->buffer_total_count) return 0;
+       return sp->buffers[id];
+}
+
+int pvr2_stream_get_ready_count(struct pvr2_stream *sp)
+{
+       return sp->r_count;
+}
+
+int pvr2_stream_get_idle_count(struct pvr2_stream *sp)
+{
+       return sp->i_count;
+}
+
+void pvr2_stream_flush(struct pvr2_stream *sp)
+{
+       mutex_lock(&sp->mutex); do {
+               pvr2_stream_internal_flush(sp);
+       } while(0); mutex_unlock(&sp->mutex);
+}
+
+void pvr2_stream_kill(struct pvr2_stream *sp)
+{
+       struct pvr2_buffer *bp;
+       mutex_lock(&sp->mutex); do {
+               pvr2_stream_internal_flush(sp);
+               while ((bp = pvr2_stream_get_ready_buffer(sp)) != 0) {
+                       pvr2_buffer_set_idle(bp);
+               }
+               if (sp->buffer_total_count != sp->buffer_target_count) {
+                       pvr2_stream_achieve_buffer_count(sp);
+               }
+       } while(0); mutex_unlock(&sp->mutex);
+}
+
+int pvr2_buffer_queue(struct pvr2_buffer *bp)
+{
+#undef SEED_BUFFER
+#ifdef SEED_BUFFER
+       unsigned int idx;
+       unsigned int val;
+#endif
+       int ret = 0;
+       struct pvr2_stream *sp;
+       if (!bp) return -EINVAL;
+       sp = bp->stream;
+       mutex_lock(&sp->mutex); do {
+               pvr2_buffer_wipe(bp);
+               if (!sp->dev) {
+                       ret = -EIO;
+                       break;
+               }
+               pvr2_buffer_set_queued(bp);
+#ifdef SEED_BUFFER
+               for (idx = 0; idx < (bp->max_count) / 4; idx++) {
+                       val = bp->id << 24;
+                       val |= idx;
+                       ((unsigned int *)(bp->ptr))[idx] = val;
+               }
+#endif
+               bp->status = -EINPROGRESS;
+               usb_fill_bulk_urb(bp->purb,      // struct urb *urb
+                                 sp->dev,       // struct usb_device *dev
+                                 // endpoint (below)
+                                 usb_rcvbulkpipe(sp->dev,sp->endpoint),
+                                 bp->ptr,       // void *transfer_buffer
+                                 bp->max_count, // int buffer_length
+                                 buffer_complete,
+                                 bp);
+               usb_submit_urb(bp->purb,GFP_KERNEL);
+       } while(0); mutex_unlock(&sp->mutex);
+       return ret;
+}
+
+int pvr2_buffer_idle(struct pvr2_buffer *bp)
+{
+       struct pvr2_stream *sp;
+       if (!bp) return -EINVAL;
+       sp = bp->stream;
+       mutex_lock(&sp->mutex); do {
+               pvr2_buffer_wipe(bp);
+               pvr2_buffer_set_idle(bp);
+               if (sp->buffer_total_count != sp->buffer_target_count) {
+                       pvr2_stream_achieve_buffer_count(sp);
+               }
+       } while(0); mutex_unlock(&sp->mutex);
+       return 0;
+}
+
+int pvr2_buffer_set_buffer(struct pvr2_buffer *bp,void *ptr,unsigned int cnt)
+{
+       int ret = 0;
+       unsigned long irq_flags;
+       struct pvr2_stream *sp;
+       if (!bp) return -EINVAL;
+       sp = bp->stream;
+       mutex_lock(&sp->mutex); do {
+               spin_lock_irqsave(&sp->list_lock,irq_flags);
+               if (bp->state != pvr2_buffer_state_idle) {
+                       ret = -EPERM;
+               } else {
+                       bp->ptr = ptr;
+                       bp->stream->i_bcount -= bp->max_count;
+                       bp->max_count = cnt;
+                       bp->stream->i_bcount += bp->max_count;
+                       pvr2_trace(PVR2_TRACE_BUF_FLOW,
+                                  "/*---TRACE_FLOW---*/ bufferPool    "
+                                  " %8s cap cap=%07d cnt=%02d",
+                                  pvr2_buffer_state_decode(
+                                          pvr2_buffer_state_idle),
+                                  bp->stream->i_bcount,bp->stream->i_count);
+               }
+               spin_unlock_irqrestore(&sp->list_lock,irq_flags);
+       } while(0); mutex_unlock(&sp->mutex);
+       return ret;
+}
+
+unsigned int pvr2_buffer_get_count(struct pvr2_buffer *bp)
+{
+       return bp->used_count;
+}
+
+int pvr2_buffer_get_status(struct pvr2_buffer *bp)
+{
+       return bp->status;
+}
+
+enum pvr2_buffer_state pvr2_buffer_get_state(struct pvr2_buffer *bp)
+{
+       return bp->state;
+}
+
+int pvr2_buffer_get_id(struct pvr2_buffer *bp)
+{
+       return bp->id;
+}
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-io.h b/drivers/media/video/pvrusb2/pvrusb2-io.h
new file mode 100644 (file)
index 0000000..65e1138
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_IO_H
+#define __PVRUSB2_IO_H
+
+#include <linux/usb.h>
+#include <linux/list.h>
+
+typedef void (*pvr2_stream_callback)(void *);
+
+enum pvr2_buffer_state {
+       pvr2_buffer_state_none = 0,   // Not on any list
+       pvr2_buffer_state_idle = 1,   // Buffer is ready to be used again
+       pvr2_buffer_state_queued = 2, // Buffer has been queued for filling
+       pvr2_buffer_state_ready = 3,  // Buffer has data available
+};
+
+struct pvr2_stream;
+struct pvr2_buffer;
+
+const char *pvr2_buffer_state_decode(enum pvr2_buffer_state);
+
+/* Initialize / tear down stream structure */
+struct pvr2_stream *pvr2_stream_create(void);
+void pvr2_stream_destroy(struct pvr2_stream *);
+void pvr2_stream_setup(struct pvr2_stream *,
+                      struct usb_device *dev,int endpoint,
+                      unsigned int tolerance);
+void pvr2_stream_set_callback(struct pvr2_stream *,
+                             pvr2_stream_callback func,
+                             void *data);
+
+/* Query / set the nominal buffer count */
+int pvr2_stream_get_buffer_count(struct pvr2_stream *);
+int pvr2_stream_set_buffer_count(struct pvr2_stream *,unsigned int);
+
+/* Get a pointer to a buffer that is either idle, ready, or is specified
+   named. */
+struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *);
+struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *);
+struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp,int id);
+
+/* Find out how many buffers are idle or ready */
+int pvr2_stream_get_idle_count(struct pvr2_stream *);
+int pvr2_stream_get_ready_count(struct pvr2_stream *);
+
+/* Kill all pending operations */
+void pvr2_stream_flush(struct pvr2_stream *);
+
+/* Kill all pending buffers and throw away any ready buffers as well */
+void pvr2_stream_kill(struct pvr2_stream *);
+
+/* Set up the actual storage for a buffer */
+int pvr2_buffer_set_buffer(struct pvr2_buffer *,void *ptr,unsigned int cnt);
+
+/* Find out size of data in the given ready buffer */
+unsigned int pvr2_buffer_get_count(struct pvr2_buffer *);
+
+/* Retrieve completion code for given ready buffer */
+int pvr2_buffer_get_status(struct pvr2_buffer *);
+
+/* Retrieve state of given buffer */
+enum pvr2_buffer_state pvr2_buffer_get_state(struct pvr2_buffer *);
+
+/* Retrieve ID of given buffer */
+int pvr2_buffer_get_id(struct pvr2_buffer *);
+
+/* Start reading into given buffer (kill it if needed) */
+int pvr2_buffer_queue(struct pvr2_buffer *);
+
+/* Move buffer back to idle pool (kill it if needed) */
+int pvr2_buffer_idle(struct pvr2_buffer *);
+
+#endif /* __PVRUSB2_IO_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-ioread.c b/drivers/media/video/pvrusb2/pvrusb2-ioread.c
new file mode 100644 (file)
index 0000000..49da062
--- /dev/null
@@ -0,0 +1,513 @@
+/*
+ *
+ *  $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-ioread.h"
+#include "pvrusb2-debug.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <asm/uaccess.h>
+
+#define BUFFER_COUNT 32
+#define BUFFER_SIZE PAGE_ALIGN(0x4000)
+
+struct pvr2_ioread {
+       struct pvr2_stream *stream;
+       char *buffer_storage[BUFFER_COUNT];
+       char *sync_key_ptr;
+       unsigned int sync_key_len;
+       unsigned int sync_buf_offs;
+       unsigned int sync_state;
+       unsigned int sync_trashed_count;
+       int enabled;         // Streaming is on
+       int spigot_open;     // OK to pass data to client
+       int stream_running;  // Passing data to client now
+
+       /* State relevant to current buffer being read */
+       struct pvr2_buffer *c_buf;
+       char *c_data_ptr;
+       unsigned int c_data_len;
+       unsigned int c_data_offs;
+       struct mutex mutex;
+};
+
+static int pvr2_ioread_init(struct pvr2_ioread *cp)
+{
+       unsigned int idx;
+
+       cp->stream = 0;
+       mutex_init(&cp->mutex);
+
+       for (idx = 0; idx < BUFFER_COUNT; idx++) {
+               cp->buffer_storage[idx] = kmalloc(BUFFER_SIZE,GFP_KERNEL);
+               if (!(cp->buffer_storage[idx])) break;
+       }
+
+       if (idx < BUFFER_COUNT) {
+               // An allocation appears to have failed
+               for (idx = 0; idx < BUFFER_COUNT; idx++) {
+                       if (!(cp->buffer_storage[idx])) continue;
+                       kfree(cp->buffer_storage[idx]);
+               }
+               return -ENOMEM;
+       }
+       return 0;
+}
+
+static void pvr2_ioread_done(struct pvr2_ioread *cp)
+{
+       unsigned int idx;
+
+       pvr2_ioread_setup(cp,0);
+       for (idx = 0; idx < BUFFER_COUNT; idx++) {
+               if (!(cp->buffer_storage[idx])) continue;
+               kfree(cp->buffer_storage[idx]);
+       }
+}
+
+struct pvr2_ioread *pvr2_ioread_create(void)
+{
+       struct pvr2_ioread *cp;
+       cp = kmalloc(sizeof(*cp),GFP_KERNEL);
+       if (!cp) return 0;
+       pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_create id=%p",cp);
+       memset(cp,0,sizeof(*cp));
+       if (pvr2_ioread_init(cp) < 0) {
+               kfree(cp);
+               return 0;
+       }
+       return cp;
+}
+
+void pvr2_ioread_destroy(struct pvr2_ioread *cp)
+{
+       if (!cp) return;
+       pvr2_ioread_done(cp);
+       pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_destroy id=%p",cp);
+       if (cp->sync_key_ptr) {
+               kfree(cp->sync_key_ptr);
+               cp->sync_key_ptr = 0;
+       }
+       kfree(cp);
+}
+
+void pvr2_ioread_set_sync_key(struct pvr2_ioread *cp,
+                             const char *sync_key_ptr,
+                             unsigned int sync_key_len)
+{
+       if (!cp) return;
+
+       if (!sync_key_ptr) sync_key_len = 0;
+       if ((sync_key_len == cp->sync_key_len) &&
+           ((!sync_key_len) ||
+            (!memcmp(sync_key_ptr,cp->sync_key_ptr,sync_key_len)))) return;
+
+       if (sync_key_len != cp->sync_key_len) {
+               if (cp->sync_key_ptr) {
+                       kfree(cp->sync_key_ptr);
+                       cp->sync_key_ptr = 0;
+               }
+               cp->sync_key_len = 0;
+               if (sync_key_len) {
+                       cp->sync_key_ptr = kmalloc(sync_key_len,GFP_KERNEL);
+                       if (cp->sync_key_ptr) {
+                               cp->sync_key_len = sync_key_len;
+                       }
+               }
+       }
+       if (!cp->sync_key_len) return;
+       memcpy(cp->sync_key_ptr,sync_key_ptr,cp->sync_key_len);
+}
+
+static void pvr2_ioread_stop(struct pvr2_ioread *cp)
+{
+       if (!(cp->enabled)) return;
+       pvr2_trace(PVR2_TRACE_START_STOP,
+                  "/*---TRACE_READ---*/ pvr2_ioread_stop id=%p",cp);
+       pvr2_stream_kill(cp->stream);
+       cp->c_buf = 0;
+       cp->c_data_ptr = 0;
+       cp->c_data_len = 0;
+       cp->c_data_offs = 0;
+       cp->enabled = 0;
+       cp->stream_running = 0;
+       cp->spigot_open = 0;
+       if (cp->sync_state) {
+               pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                          "/*---TRACE_READ---*/ sync_state <== 0");
+               cp->sync_state = 0;
+       }
+}
+
+static int pvr2_ioread_start(struct pvr2_ioread *cp)
+{
+       int stat;
+       struct pvr2_buffer *bp;
+       if (cp->enabled) return 0;
+       if (!(cp->stream)) return 0;
+       pvr2_trace(PVR2_TRACE_START_STOP,
+                  "/*---TRACE_READ---*/ pvr2_ioread_start id=%p",cp);
+       while ((bp = pvr2_stream_get_idle_buffer(cp->stream)) != 0) {
+               stat = pvr2_buffer_queue(bp);
+               if (stat < 0) {
+                       pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                                  "/*---TRACE_READ---*/"
+                                  " pvr2_ioread_start id=%p"
+                                  " error=%d",
+                                  cp,stat);
+                       pvr2_ioread_stop(cp);
+                       return stat;
+               }
+       }
+       cp->enabled = !0;
+       cp->c_buf = 0;
+       cp->c_data_ptr = 0;
+       cp->c_data_len = 0;
+       cp->c_data_offs = 0;
+       cp->stream_running = 0;
+       if (cp->sync_key_len) {
+               pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                          "/*---TRACE_READ---*/ sync_state <== 1");
+               cp->sync_state = 1;
+               cp->sync_trashed_count = 0;
+               cp->sync_buf_offs = 0;
+       }
+       cp->spigot_open = 0;
+       return 0;
+}
+
+struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *cp)
+{
+       return cp->stream;
+}
+
+int pvr2_ioread_setup(struct pvr2_ioread *cp,struct pvr2_stream *sp)
+{
+       int ret;
+       unsigned int idx;
+       struct pvr2_buffer *bp;
+
+       mutex_lock(&cp->mutex); do {
+               if (cp->stream) {
+                       pvr2_trace(PVR2_TRACE_START_STOP,
+                                  "/*---TRACE_READ---*/"
+                                  " pvr2_ioread_setup (tear-down) id=%p",cp);
+                       pvr2_ioread_stop(cp);
+                       pvr2_stream_kill(cp->stream);
+                       pvr2_stream_set_buffer_count(cp->stream,0);
+                       cp->stream = 0;
+               }
+               if (sp) {
+                       pvr2_trace(PVR2_TRACE_START_STOP,
+                                  "/*---TRACE_READ---*/"
+                                  " pvr2_ioread_setup (setup) id=%p",cp);
+                       pvr2_stream_kill(sp);
+                       ret = pvr2_stream_set_buffer_count(sp,BUFFER_COUNT);
+                       if (ret < 0) return ret;
+                       for (idx = 0; idx < BUFFER_COUNT; idx++) {
+                               bp = pvr2_stream_get_buffer(sp,idx);
+                               pvr2_buffer_set_buffer(bp,
+                                                      cp->buffer_storage[idx],
+                                                      BUFFER_SIZE);
+                       }
+                       cp->stream = sp;
+               }
+       } while (0); mutex_unlock(&cp->mutex);
+
+       return 0;
+}
+
+int pvr2_ioread_set_enabled(struct pvr2_ioread *cp,int fl)
+{
+       int ret = 0;
+       if ((!fl) == (!(cp->enabled))) return ret;
+
+       mutex_lock(&cp->mutex); do {
+               if (fl) {
+                       ret = pvr2_ioread_start(cp);
+               } else {
+                       pvr2_ioread_stop(cp);
+               }
+       } while (0); mutex_unlock(&cp->mutex);
+       return ret;
+}
+
+int pvr2_ioread_get_enabled(struct pvr2_ioread *cp)
+{
+       return cp->enabled != 0;
+}
+
+int pvr2_ioread_get_buffer(struct pvr2_ioread *cp)
+{
+       int stat;
+
+       while (cp->c_data_len <= cp->c_data_offs) {
+               if (cp->c_buf) {
+                       // Flush out current buffer first.
+                       stat = pvr2_buffer_queue(cp->c_buf);
+                       if (stat < 0) {
+                               // Streaming error...
+                               pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                                          "/*---TRACE_READ---*/"
+                                          " pvr2_ioread_read id=%p"
+                                          " queue_error=%d",
+                                          cp,stat);
+                               pvr2_ioread_stop(cp);
+                               return 0;
+                       }
+                       cp->c_buf = 0;
+                       cp->c_data_ptr = 0;
+                       cp->c_data_len = 0;
+                       cp->c_data_offs = 0;
+               }
+               // Now get a freshly filled buffer.
+               cp->c_buf = pvr2_stream_get_ready_buffer(cp->stream);
+               if (!cp->c_buf) break; // Nothing ready; done.
+               cp->c_data_len = pvr2_buffer_get_count(cp->c_buf);
+               if (!cp->c_data_len) {
+                       // Nothing transferred.  Was there an error?
+                       stat = pvr2_buffer_get_status(cp->c_buf);
+                       if (stat < 0) {
+                               // Streaming error...
+                               pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                                          "/*---TRACE_READ---*/"
+                                          " pvr2_ioread_read id=%p"
+                                          " buffer_error=%d",
+                                          cp,stat);
+                               pvr2_ioread_stop(cp);
+                               // Give up.
+                               return 0;
+                       }
+                       // Start over...
+                       continue;
+               }
+               cp->c_data_offs = 0;
+               cp->c_data_ptr = cp->buffer_storage[
+                       pvr2_buffer_get_id(cp->c_buf)];
+       }
+       return !0;
+}
+
+void pvr2_ioread_filter(struct pvr2_ioread *cp)
+{
+       unsigned int idx;
+       if (!cp->enabled) return;
+       if (cp->sync_state != 1) return;
+
+       // Search the stream for our synchronization key.  This is made
+       // complicated by the fact that in order to be honest with
+       // ourselves here we must search across buffer boundaries...
+       mutex_lock(&cp->mutex); while (1) {
+               // Ensure we have a buffer
+               if (!pvr2_ioread_get_buffer(cp)) break;
+               if (!cp->c_data_len) break;
+
+               // Now walk the buffer contents until we match the key or
+               // run out of buffer data.
+               for (idx = cp->c_data_offs; idx < cp->c_data_len; idx++) {
+                       if (cp->sync_buf_offs >= cp->sync_key_len) break;
+                       if (cp->c_data_ptr[idx] ==
+                           cp->sync_key_ptr[cp->sync_buf_offs]) {
+                               // Found the next key byte
+                               (cp->sync_buf_offs)++;
+                       } else {
+                               // Whoops, mismatched.  Start key over...
+                               cp->sync_buf_offs = 0;
+                       }
+               }
+
+               // Consume what we've walked through
+               cp->c_data_offs += idx;
+               cp->sync_trashed_count += idx;
+
+               // If we've found the key, then update state and get out.
+               if (cp->sync_buf_offs >= cp->sync_key_len) {
+                       cp->sync_trashed_count -= cp->sync_key_len;
+                       pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                                  "/*---TRACE_READ---*/"
+                                  " sync_state <== 2 (skipped %u bytes)",
+                                  cp->sync_trashed_count);
+                       cp->sync_state = 2;
+                       cp->sync_buf_offs = 0;
+                       break;
+               }
+
+               if (cp->c_data_offs < cp->c_data_len) {
+                       // Sanity check - should NEVER get here
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "ERROR: pvr2_ioread filter sync problem"
+                                  " len=%u offs=%u",
+                                  cp->c_data_len,cp->c_data_offs);
+                       // Get out so we don't get stuck in an infinite
+                       // loop.
+                       break;
+               }
+
+               continue; // (for clarity)
+       } mutex_unlock(&cp->mutex);
+}
+
+int pvr2_ioread_avail(struct pvr2_ioread *cp)
+{
+       int ret;
+       if (!(cp->enabled)) {
+               // Stream is not enabled; so this is an I/O error
+               return -EIO;
+       }
+
+       if (cp->sync_state == 1) {
+               pvr2_ioread_filter(cp);
+               if (cp->sync_state == 1) return -EAGAIN;
+       }
+
+       ret = 0;
+       if (cp->stream_running) {
+               if (!pvr2_stream_get_ready_count(cp->stream)) {
+                       // No data available at all right now.
+                       ret = -EAGAIN;
+               }
+       } else {
+               if (pvr2_stream_get_ready_count(cp->stream) < BUFFER_COUNT/2) {
+                       // Haven't buffered up enough yet; try again later
+                       ret = -EAGAIN;
+               }
+       }
+
+       if ((!(cp->spigot_open)) != (!(ret == 0))) {
+               cp->spigot_open = (ret == 0);
+               pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                          "/*---TRACE_READ---*/ data is %s",
+                          cp->spigot_open ? "available" : "pending");
+       }
+
+       return ret;
+}
+
+int pvr2_ioread_read(struct pvr2_ioread *cp,void __user *buf,unsigned int cnt)
+{
+       unsigned int copied_cnt;
+       unsigned int bcnt;
+       const char *src;
+       int stat;
+       int ret = 0;
+       unsigned int req_cnt = cnt;
+
+       if (!cnt) {
+               pvr2_trace(PVR2_TRACE_TRAP,
+                          "/*---TRACE_READ---*/ pvr2_ioread_read id=%p"
+                          " ZERO Request? Returning zero.",cp);
+               return 0;
+       }
+
+       stat = pvr2_ioread_avail(cp);
+       if (stat < 0) return stat;
+
+       cp->stream_running = !0;
+
+       mutex_lock(&cp->mutex); do {
+
+               // Suck data out of the buffers and copy to the user
+               copied_cnt = 0;
+               if (!buf) cnt = 0;
+               while (1) {
+                       if (!pvr2_ioread_get_buffer(cp)) {
+                               ret = -EIO;
+                               break;
+                       }
+
+                       if (!cnt) break;
+
+                       if (cp->sync_state == 2) {
+                               // We're repeating the sync key data into
+                               // the stream.
+                               src = cp->sync_key_ptr + cp->sync_buf_offs;
+                               bcnt = cp->sync_key_len - cp->sync_buf_offs;
+                       } else {
+                               // Normal buffer copy
+                               src = cp->c_data_ptr + cp->c_data_offs;
+                               bcnt = cp->c_data_len - cp->c_data_offs;
+                       }
+
+                       if (!bcnt) break;
+
+                       // Don't run past user's buffer
+                       if (bcnt > cnt) bcnt = cnt;
+
+                       if (copy_to_user(buf,src,bcnt)) {
+                               // User supplied a bad pointer?
+                               // Give up - this *will* cause data
+                               // to be lost.
+                               ret = -EFAULT;
+                               break;
+                       }
+                       cnt -= bcnt;
+                       buf += bcnt;
+                       copied_cnt += bcnt;
+
+                       if (cp->sync_state == 2) {
+                               // Update offset inside sync key that we're
+                               // repeating back out.
+                               cp->sync_buf_offs += bcnt;
+                               if (cp->sync_buf_offs >= cp->sync_key_len) {
+                                       // Consumed entire key; switch mode
+                                       // to normal.
+                                       pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                                                  "/*---TRACE_READ---*/"
+                                                  " sync_state <== 0");
+                                       cp->sync_state = 0;
+                               }
+                       } else {
+                               // Update buffer offset.
+                               cp->c_data_offs += bcnt;
+                       }
+               }
+
+       } while (0); mutex_unlock(&cp->mutex);
+
+       if (!ret) {
+               if (copied_cnt) {
+                       // If anything was copied, return that count
+                       ret = copied_cnt;
+               } else {
+                       // Nothing copied; suggest to caller that another
+                       // attempt should be tried again later
+                       ret = -EAGAIN;
+               }
+       }
+
+       pvr2_trace(PVR2_TRACE_DATA_FLOW,
+                  "/*---TRACE_READ---*/ pvr2_ioread_read"
+                  " id=%p request=%d result=%d",
+                  cp,req_cnt,ret);
+       return ret;
+}
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-ioread.h b/drivers/media/video/pvrusb2/pvrusb2-ioread.h
new file mode 100644 (file)
index 0000000..6b00259
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_IOREAD_H
+#define __PVRUSB2_IOREAD_H
+
+#include "pvrusb2-io.h"
+
+struct pvr2_ioread;
+
+struct pvr2_ioread *pvr2_ioread_create(void);
+void pvr2_ioread_destroy(struct pvr2_ioread *);
+int pvr2_ioread_setup(struct pvr2_ioread *,struct pvr2_stream *);
+struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *);
+void pvr2_ioread_set_sync_key(struct pvr2_ioread *,
+                             const char *sync_key_ptr,
+                             unsigned int sync_key_len);
+int pvr2_ioread_set_enabled(struct pvr2_ioread *,int fl);
+int pvr2_ioread_get_enabled(struct pvr2_ioread *);
+int pvr2_ioread_read(struct pvr2_ioread *,void __user *buf,unsigned int cnt);
+int pvr2_ioread_avail(struct pvr2_ioread *);
+
+#endif /* __PVRUSB2_IOREAD_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-main.c b/drivers/media/video/pvrusb2/pvrusb2-main.c
new file mode 100644 (file)
index 0000000..d3c538c
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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 <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/smp_lock.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-context.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-v4l2.h"
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+#include "pvrusb2-sysfs.h"
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+#define DRIVER_AUTHOR "Mike Isely <isely@pobox.com>"
+#define DRIVER_DESC "Hauppauge WinTV-PVR-USB2 MPEG2 Encoder/Tuner"
+#define DRIVER_VERSION "V4L in-tree version"
+
+#define DEFAULT_DEBUG_MASK (PVR2_TRACE_ERROR_LEGS| \
+                           PVR2_TRACE_INFO| \
+                           PVR2_TRACE_TOLERANCE| \
+                           PVR2_TRACE_TRAP| \
+                           PVR2_TRACE_FIRMWARE| \
+                           PVR2_TRACE_EEPROM | \
+                           PVR2_TRACE_INIT | \
+                           PVR2_TRACE_I2C | \
+                           PVR2_TRACE_CHIPS | \
+                           PVR2_TRACE_START_STOP | \
+                           PVR2_TRACE_CTL | \
+                           PVR2_TRACE_DEBUGIFC | \
+                           0)
+
+int pvrusb2_debug = DEFAULT_DEBUG_MASK;
+
+module_param_named(debug,pvrusb2_debug,int,S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug trace mask");
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+static struct pvr2_sysfs_class *class_ptr = 0;
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+static void pvr_setup_attach(struct pvr2_context *pvr)
+{
+       /* Create association with v4l layer */
+       pvr2_v4l2_create(pvr);
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+       pvr2_sysfs_create(pvr,class_ptr);
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+}
+
+static int pvr_probe(struct usb_interface *intf,
+                    const struct usb_device_id *devid)
+{
+       struct pvr2_context *pvr;
+
+       /* Create underlying hardware interface */
+       pvr = pvr2_context_create(intf,devid,pvr_setup_attach);
+       if (!pvr) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "Failed to create hdw handler");
+               return -ENOMEM;
+       }
+
+       pvr2_trace(PVR2_TRACE_INIT,"pvr_probe(pvr=%p)",pvr);
+
+       usb_set_intfdata(intf, pvr);
+
+       return 0;
+}
+
+/*
+ * pvr_disconnect()
+ *
+ */
+static void pvr_disconnect(struct usb_interface *intf)
+{
+       struct pvr2_context *pvr = usb_get_intfdata(intf);
+
+       pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) BEGIN",pvr);
+
+       usb_set_intfdata (intf, NULL);
+       pvr2_context_disconnect(pvr);
+
+       pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) DONE",pvr);
+
+}
+
+static struct usb_driver pvr_driver = {
+       name:           "pvrusb2",
+       id_table:       pvr2_device_table,
+       probe:          pvr_probe,
+       disconnect:     pvr_disconnect
+};
+
+/*
+ * pvr_init() / pvr_exit()
+ *
+ * This code is run to initialize/exit the driver.
+ *
+ */
+static int __init pvr_init(void)
+{
+       int ret;
+
+       pvr2_trace(PVR2_TRACE_INIT,"pvr_init");
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+       class_ptr = pvr2_sysfs_class_create();
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+       ret = usb_register(&pvr_driver);
+
+       if (ret == 0)
+               info(DRIVER_DESC " : " DRIVER_VERSION);
+       if (pvrusb2_debug) info("Debug mask is %d (0x%x)",
+                               pvrusb2_debug,pvrusb2_debug);
+
+       return ret;
+}
+
+static void __exit pvr_exit(void)
+{
+
+       pvr2_trace(PVR2_TRACE_INIT,"pvr_exit");
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+       pvr2_sysfs_class_destroy(class_ptr);
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+       usb_deregister(&pvr_driver);
+}
+
+module_init(pvr_init);
+module_exit(pvr_exit);
+
+/* Mike Isely <mcisely@pobox.com> 11-Mar-2006: See pvrusb2-hdw.c for
+   MODULE_DEVICE_TABLE().  We have to declare that attribute there
+   because that's where the device table actually is now and it seems
+   that certain gcc configurations get angry if MODULE_DEVICE_TABLE()
+   is used on what ends up being an external symbol. */
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-std.c b/drivers/media/video/pvrusb2/pvrusb2-std.c
new file mode 100644 (file)
index 0000000..64ba223
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+ *
+ *  $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-std.h"
+#include "pvrusb2-debug.h"
+
+struct std_name {
+       const char *name;
+       v4l2_std_id id;
+};
+
+
+#define CSTD_PAL \
+       (V4L2_STD_PAL_B| \
+        V4L2_STD_PAL_B1| \
+        V4L2_STD_PAL_G| \
+        V4L2_STD_PAL_H| \
+        V4L2_STD_PAL_I| \
+        V4L2_STD_PAL_D| \
+        V4L2_STD_PAL_D1| \
+        V4L2_STD_PAL_K| \
+        V4L2_STD_PAL_M| \
+        V4L2_STD_PAL_N| \
+        V4L2_STD_PAL_Nc| \
+        V4L2_STD_PAL_60)
+
+#define CSTD_NTSC \
+       (V4L2_STD_NTSC_M| \
+        V4L2_STD_NTSC_M_JP| \
+        V4L2_STD_NTSC_M_KR| \
+        V4L2_STD_NTSC_443)
+
+#define CSTD_SECAM \
+       (V4L2_STD_SECAM_B| \
+        V4L2_STD_SECAM_D| \
+        V4L2_STD_SECAM_G| \
+        V4L2_STD_SECAM_H| \
+        V4L2_STD_SECAM_K| \
+        V4L2_STD_SECAM_K1| \
+        V4L2_STD_SECAM_L| \
+        V4L2_STD_SECAM_LC)
+
+#define TSTD_B   (V4L2_STD_PAL_B|V4L2_STD_SECAM_B)
+#define TSTD_B1  (V4L2_STD_PAL_B1)
+#define TSTD_D   (V4L2_STD_PAL_D|V4L2_STD_SECAM_D)
+#define TSTD_D1  (V4L2_STD_PAL_D1)
+#define TSTD_G   (V4L2_STD_PAL_G|V4L2_STD_SECAM_G)
+#define TSTD_H   (V4L2_STD_PAL_H|V4L2_STD_SECAM_H)
+#define TSTD_I   (V4L2_STD_PAL_I)
+#define TSTD_K   (V4L2_STD_PAL_K|V4L2_STD_SECAM_K)
+#define TSTD_K1  (V4L2_STD_SECAM_K1)
+#define TSTD_L   (V4L2_STD_SECAM_L)
+#define TSTD_M   (V4L2_STD_PAL_M|V4L2_STD_NTSC_M)
+#define TSTD_N   (V4L2_STD_PAL_N)
+#define TSTD_Nc  (V4L2_STD_PAL_Nc)
+#define TSTD_60  (V4L2_STD_PAL_60)
+
+#define CSTD_ALL (CSTD_PAL|CSTD_NTSC|CSTD_SECAM)
+
+/* Mapping of standard bits to color system */
+const static struct std_name std_groups[] = {
+       {"PAL",CSTD_PAL},
+       {"NTSC",CSTD_NTSC},
+       {"SECAM",CSTD_SECAM},
+};
+
+/* Mapping of standard bits to modulation system */
+const static struct std_name std_items[] = {
+       {"B",TSTD_B},
+       {"B1",TSTD_B1},
+       {"D",TSTD_D},
+       {"D1",TSTD_D1},
+       {"G",TSTD_G},
+       {"H",TSTD_H},
+       {"I",TSTD_I},
+       {"K",TSTD_K},
+       {"K1",TSTD_K1},
+       {"L",TSTD_L},
+       {"LC",V4L2_STD_SECAM_LC},
+       {"M",TSTD_M},
+       {"Mj",V4L2_STD_NTSC_M_JP},
+       {"443",V4L2_STD_NTSC_443},
+       {"Mk",V4L2_STD_NTSC_M_KR},
+       {"N",TSTD_N},
+       {"Nc",TSTD_Nc},
+       {"60",TSTD_60},
+};
+
+
+// Search an array of std_name structures and return a pointer to the
+// element with the matching name.
+static const struct std_name *find_std_name(const struct std_name *arrPtr,
+                                           unsigned int arrSize,
+                                           const char *bufPtr,
+                                           unsigned int bufSize)
+{
+       unsigned int idx;
+       const struct std_name *p;
+       for (idx = 0; idx < arrSize; idx++) {
+               p = arrPtr + idx;
+               if (strlen(p->name) != bufSize) continue;
+               if (!memcmp(bufPtr,p->name,bufSize)) return p;
+       }
+       return 0;
+}
+
+
+int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr,
+                      unsigned int bufSize)
+{
+       v4l2_std_id id = 0;
+       v4l2_std_id cmsk = 0;
+       v4l2_std_id t;
+       int mMode = 0;
+       unsigned int cnt;
+       char ch;
+       const struct std_name *sp;
+
+       while (bufSize) {
+               if (!mMode) {
+                       cnt = 0;
+                       while ((cnt < bufSize) && (bufPtr[cnt] != '-')) cnt++;
+                       if (cnt >= bufSize) return 0; // No more characters
+                       sp = find_std_name(
+                               std_groups,
+                               sizeof(std_groups)/sizeof(std_groups[0]),
+                               bufPtr,cnt);
+                       if (!sp) return 0; // Illegal color system name
+                       cnt++;
+                       bufPtr += cnt;
+                       bufSize -= cnt;
+                       mMode = !0;
+                       cmsk = sp->id;
+                       continue;
+               }
+               cnt = 0;
+               while (cnt < bufSize) {
+                       ch = bufPtr[cnt];
+                       if (ch == ';') {
+                               mMode = 0;
+                               break;
+                       }
+                       if (ch == '/') break;
+                       cnt++;
+               }
+               sp = find_std_name(std_items,
+                                  sizeof(std_items)/sizeof(std_items[0]),
+                                  bufPtr,cnt);
+               if (!sp) return 0; // Illegal modulation system ID
+               t = sp->id & cmsk;
+               if (!t) return 0; // Specific color + modulation system illegal
+               id |= t;
+               if (cnt < bufSize) cnt++;
+               bufPtr += cnt;
+               bufSize -= cnt;
+       }
+
+       if (idPtr) *idPtr = id;
+       return !0;
+}
+
+
+unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize,
+                               v4l2_std_id id)
+{
+       unsigned int idx1,idx2;
+       const struct std_name *ip,*gp;
+       int gfl,cfl;
+       unsigned int c1,c2;
+       cfl = 0;
+       c1 = 0;
+       for (idx1 = 0;
+            idx1 < sizeof(std_groups)/sizeof(std_groups[0]);
+            idx1++) {
+               gp = std_groups + idx1;
+               gfl = 0;
+               for (idx2 = 0;
+                    idx2 < sizeof(std_items)/sizeof(std_items[0]);
+                    idx2++) {
+                       ip = std_items + idx2;
+                       if (!(gp->id & ip->id & id)) continue;
+                       if (!gfl) {
+                               if (cfl) {
+                                       c2 = scnprintf(bufPtr,bufSize,";");
+                                       c1 += c2;
+                                       bufSize -= c2;
+                                       bufPtr += c2;
+                               }
+                               cfl = !0;
+                               c2 = scnprintf(bufPtr,bufSize,
+                                              "%s-",gp->name);
+                               gfl = !0;
+                       } else {
+                               c2 = scnprintf(bufPtr,bufSize,"/");
+                       }
+                       c1 += c2;
+                       bufSize -= c2;
+                       bufPtr += c2;
+                       c2 = scnprintf(bufPtr,bufSize,
+                                      ip->name);
+                       c1 += c2;
+                       bufSize -= c2;
+                       bufPtr += c2;
+               }
+       }
+       return c1;
+}
+
+
+// Template data for possible enumerated video standards.  Here we group
+// standards which share common frame rates and resolution.
+static struct v4l2_standard generic_standards[] = {
+       {
+               .id             = (TSTD_B|TSTD_B1|
+                                  TSTD_D|TSTD_D1|
+                                  TSTD_G|
+                                  TSTD_H|
+                                  TSTD_I|
+                                  TSTD_K|TSTD_K1|
+                                  TSTD_L|
+                                  V4L2_STD_SECAM_LC |
+                                  TSTD_N|TSTD_Nc),
+               .frameperiod    =
+               {
+                       .numerator  = 1,
+                       .denominator= 25
+               },
+               .framelines     = 625,
+               .reserved       = {0,0,0,0}
+       }, {
+               .id             = (TSTD_M|
+                                  V4L2_STD_NTSC_M_JP|
+                                  V4L2_STD_NTSC_M_KR),
+               .frameperiod    =
+               {
+                       .numerator  = 1001,
+                       .denominator= 30000
+               },
+               .framelines     = 525,
+               .reserved       = {0,0,0,0}
+       }, { // This is a total wild guess
+               .id             = (TSTD_60),
+               .frameperiod    =
+               {
+                       .numerator  = 1001,
+                       .denominator= 30000
+               },
+               .framelines     = 525,
+               .reserved       = {0,0,0,0}
+       }, { // This is total wild guess
+               .id             = V4L2_STD_NTSC_443,
+               .frameperiod    =
+               {
+                       .numerator  = 1001,
+                       .denominator= 30000
+               },
+               .framelines     = 525,
+               .reserved       = {0,0,0,0}
+       }
+};
+
+#define generic_standards_cnt (sizeof(generic_standards)/sizeof(generic_standards[0]))
+
+static struct v4l2_standard *match_std(v4l2_std_id id)
+{
+       unsigned int idx;
+       for (idx = 0; idx < generic_standards_cnt; idx++) {
+               if (generic_standards[idx].id & id) {
+                       return generic_standards + idx;
+               }
+       }
+       return 0;
+}
+
+static int pvr2_std_fill(struct v4l2_standard *std,v4l2_std_id id)
+{
+       struct v4l2_standard *template;
+       int idx;
+       unsigned int bcnt;
+       template = match_std(id);
+       if (!template) return 0;
+       idx = std->index;
+       memcpy(std,template,sizeof(*template));
+       std->index = idx;
+       std->id = id;
+       bcnt = pvr2_std_id_to_str(std->name,sizeof(std->name)-1,id);
+       std->name[bcnt] = 0;
+       pvr2_trace(PVR2_TRACE_INIT,"Set up standard idx=%u name=%s",
+                  std->index,std->name);
+       return !0;
+}
+
+/* These are special cases of combined standards that we should enumerate
+   separately if the component pieces are present. */
+static v4l2_std_id std_mixes[] = {
+       V4L2_STD_PAL_B | V4L2_STD_PAL_G,
+       V4L2_STD_PAL_D | V4L2_STD_PAL_K,
+       V4L2_STD_SECAM_B | V4L2_STD_SECAM_G,
+       V4L2_STD_SECAM_D | V4L2_STD_SECAM_K,
+};
+
+struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr,
+                                          v4l2_std_id id)
+{
+       unsigned int std_cnt = 0;
+       unsigned int idx,bcnt,idx2;
+       v4l2_std_id idmsk,cmsk,fmsk;
+       struct v4l2_standard *stddefs;
+
+       if (pvrusb2_debug & PVR2_TRACE_INIT) {
+               char buf[50];
+               bcnt = pvr2_std_id_to_str(buf,sizeof(buf),id);
+               pvr2_trace(
+                       PVR2_TRACE_INIT,"Mapping standards mask=0x%x (%.*s)",
+                       (int)id,bcnt,buf);
+       }
+
+       *countptr = 0;
+       std_cnt = 0;
+       fmsk = 0;
+       for (idmsk = 1, cmsk = id; cmsk; idmsk <<= 1) {
+               if (!(idmsk & cmsk)) continue;
+               cmsk &= ~idmsk;
+               if (match_std(idmsk)) {
+                       std_cnt++;
+                       continue;
+               }
+               fmsk |= idmsk;
+       }
+
+       for (idx2 = 0; idx2 < sizeof(std_mixes)/sizeof(std_mixes[0]); idx2++) {
+               if ((id & std_mixes[idx2]) == std_mixes[idx2]) std_cnt++;
+       }
+
+       if (fmsk) {
+               char buf[50];
+               bcnt = pvr2_std_id_to_str(buf,sizeof(buf),fmsk);
+               pvr2_trace(
+                       PVR2_TRACE_ERROR_LEGS,
+                       "WARNING:"
+                       " Failed to classify the following standard(s): %.*s",
+                       bcnt,buf);
+       }
+
+       pvr2_trace(PVR2_TRACE_INIT,"Setting up %u unique standard(s)",
+                  std_cnt);
+       if (!std_cnt) return 0; // paranoia
+
+       stddefs = kmalloc(sizeof(struct v4l2_standard) * std_cnt,
+                         GFP_KERNEL);
+       memset(stddefs,0,sizeof(struct v4l2_standard) * std_cnt);
+       for (idx = 0; idx < std_cnt; idx++) stddefs[idx].index = idx;
+
+       idx = 0;
+
+       /* Enumerate potential special cases */
+       for (idx2 = 0; ((idx2 < sizeof(std_mixes)/sizeof(std_mixes[0])) &&
+                       (idx < std_cnt)); idx2++) {
+               if (!(id & std_mixes[idx2])) continue;
+               if (pvr2_std_fill(stddefs+idx,std_mixes[idx2])) idx++;
+       }
+       /* Now enumerate individual pieces */
+       for (idmsk = 1, cmsk = id; cmsk && (idx < std_cnt); idmsk <<= 1) {
+               if (!(idmsk & cmsk)) continue;
+               cmsk &= ~idmsk;
+               if (!pvr2_std_fill(stddefs+idx,idmsk)) continue;
+               idx++;
+       }
+
+       *countptr = std_cnt;
+       return stddefs;
+}
+
+v4l2_std_id pvr2_std_get_usable(void)
+{
+       return CSTD_ALL;
+}
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-std.h b/drivers/media/video/pvrusb2/pvrusb2-std.h
new file mode 100644 (file)
index 0000000..07c3993
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_STD_H
+#define __PVRUSB2_STD_H
+
+#include <linux/videodev2.h>
+
+// Convert string describing one or more video standards into a mask of V4L
+// standard bits.  Return true if conversion succeeds otherwise return
+// false.  String is expected to be of the form: C1-x/y;C2-a/b where C1 and
+// C2 are color system names (e.g. "PAL", "NTSC") and x, y, a, and b are
+// modulation schemes (e.g. "M", "B", "G", etc).
+int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr,
+                      unsigned int bufSize);
+
+// Convert any arbitrary set of video standard bits into an unambiguous
+// readable string.  Return value is the number of bytes consumed in the
+// buffer.  The formatted string is of a form that can be parsed by our
+// sibling std_std_to_id() function.
+unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize,
+                               v4l2_std_id id);
+
+// Create an array of suitable v4l2_standard structures given a bit mask of
+// video standards to support.  The array is allocated from the heap, and
+// the number of elements is returned in the first argument.
+struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr,
+                                          v4l2_std_id id);
+
+// Return mask of which video standard bits are valid
+v4l2_std_id pvr2_std_get_usable(void);
+
+#endif /* __PVRUSB2_STD_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-sysfs.c b/drivers/media/video/pvrusb2/pvrusb2-sysfs.c
new file mode 100644 (file)
index 0000000..c756b98
--- /dev/null
@@ -0,0 +1,775 @@
+/*
+ *
+ *  $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 <linux/config.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <asm/semaphore.h>
+#include "pvrusb2-sysfs.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+#include "pvrusb2-debugifc.h"
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+#define pvr2_sysfs_trace(...) pvr2_trace(PVR2_TRACE_SYSFS,__VA_ARGS__)
+
+struct pvr2_sysfs {
+       struct pvr2_channel channel;
+       struct class_device *class_dev;
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+       struct pvr2_sysfs_debugifc *debugifc;
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+       struct pvr2_sysfs_ctl_item *item_first;
+       struct pvr2_sysfs_ctl_item *item_last;
+       struct sysfs_ops kops;
+       struct kobj_type ktype;
+       struct class_device_attribute attr_v4l_minor_number;
+       struct class_device_attribute attr_unit_number;
+};
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+struct pvr2_sysfs_debugifc {
+       struct class_device_attribute attr_debugcmd;
+       struct class_device_attribute attr_debuginfo;
+};
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+struct pvr2_sysfs_ctl_item {
+       struct class_device_attribute attr_name;
+       struct class_device_attribute attr_min;
+       struct class_device_attribute attr_max;
+       struct class_device_attribute attr_enum;
+       struct class_device_attribute attr_bits;
+       struct class_device_attribute attr_val;
+       struct class_device_attribute attr_custom;
+       struct pvr2_ctrl *cptr;
+       struct pvr2_sysfs *chptr;
+       struct pvr2_sysfs_ctl_item *item_next;
+       struct attribute *attr_gen[6];
+       struct attribute_group grp;
+       char name[80];
+};
+
+struct pvr2_sysfs_class {
+       struct class class;
+};
+
+static ssize_t show_name(int id,struct class_device *class_dev,char *buf)
+{
+       struct pvr2_ctrl *cptr;
+       struct pvr2_sysfs *sfp;
+       const char *name;
+
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id);
+       if (!cptr) return -EINVAL;
+
+       name = pvr2_ctrl_get_desc(cptr);
+       pvr2_sysfs_trace("pvr2_sysfs(%p) show_name(cid=%d) is %s",sfp,id,name);
+
+       if (!name) return -EINVAL;
+
+       return scnprintf(buf,PAGE_SIZE,"%s\n",name);
+}
+
+static ssize_t show_min(int id,struct class_device *class_dev,char *buf)
+{
+       struct pvr2_ctrl *cptr;
+       struct pvr2_sysfs *sfp;
+       long val;
+
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id);
+       if (!cptr) return -EINVAL;
+       val = pvr2_ctrl_get_min(cptr);
+
+       pvr2_sysfs_trace("pvr2_sysfs(%p) show_min(cid=%d) is %ld",sfp,id,val);
+
+       return scnprintf(buf,PAGE_SIZE,"%ld\n",val);
+}
+
+static ssize_t show_max(int id,struct class_device *class_dev,char *buf)
+{
+       struct pvr2_ctrl *cptr;
+       struct pvr2_sysfs *sfp;
+       long val;
+
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id);
+       if (!cptr) return -EINVAL;
+       val = pvr2_ctrl_get_max(cptr);
+
+       pvr2_sysfs_trace("pvr2_sysfs(%p) show_max(cid=%d) is %ld",sfp,id,val);
+
+       return scnprintf(buf,PAGE_SIZE,"%ld\n",val);
+}
+
+static ssize_t show_val_norm(int id,struct class_device *class_dev,char *buf)
+{
+       struct pvr2_ctrl *cptr;
+       struct pvr2_sysfs *sfp;
+       int val,ret;
+       unsigned int cnt = 0;
+
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id);
+       if (!cptr) return -EINVAL;
+
+       ret = pvr2_ctrl_get_value(cptr,&val);
+       if (ret < 0) return ret;
+
+       ret = pvr2_ctrl_value_to_sym(cptr,~0,val,
+                                    buf,PAGE_SIZE-1,&cnt);
+
+       pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_norm(cid=%d) is %.*s (%d)",
+                        sfp,id,cnt,buf,val);
+       buf[cnt] = '\n';
+       return cnt+1;
+}
+
+static ssize_t show_val_custom(int id,struct class_device *class_dev,char *buf)
+{
+       struct pvr2_ctrl *cptr;
+       struct pvr2_sysfs *sfp;
+       int val,ret;
+       unsigned int cnt = 0;
+
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id);
+       if (!cptr) return -EINVAL;
+
+       ret = pvr2_ctrl_get_value(cptr,&val);
+       if (ret < 0) return ret;
+
+       ret = pvr2_ctrl_custom_value_to_sym(cptr,~0,val,
+                                           buf,PAGE_SIZE-1,&cnt);
+
+       pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_custom(cid=%d) is %.*s (%d)",
+                        sfp,id,cnt,buf,val);
+       buf[cnt] = '\n';
+       return cnt+1;
+}
+
+static ssize_t show_enum(int id,struct class_device *class_dev,char *buf)
+{
+       struct pvr2_ctrl *cptr;
+       struct pvr2_sysfs *sfp;
+       long val;
+       unsigned int bcnt,ccnt,ecnt;
+
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id);
+       if (!cptr) return -EINVAL;
+       ecnt = pvr2_ctrl_get_cnt(cptr);
+       bcnt = 0;
+       for (val = 0; val < ecnt; val++) {
+               pvr2_ctrl_get_valname(cptr,val,buf+bcnt,PAGE_SIZE-bcnt,&ccnt);
+               bcnt += ccnt;
+               if (bcnt >= PAGE_SIZE) break;
+               buf[bcnt] = '\n';
+               bcnt++;
+       }
+       pvr2_sysfs_trace("pvr2_sysfs(%p) show_enum(cid=%d)",sfp,id);
+       return bcnt;
+}
+
+static ssize_t show_bits(int id,struct class_device *class_dev,char *buf)
+{
+       struct pvr2_ctrl *cptr;
+       struct pvr2_sysfs *sfp;
+       int valid_bits,msk;
+       unsigned int bcnt,ccnt;
+
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id);
+       if (!cptr) return -EINVAL;
+       valid_bits = pvr2_ctrl_get_mask(cptr);
+       bcnt = 0;
+       for (msk = 1; valid_bits; msk <<= 1) {
+               if (!(msk & valid_bits)) continue;
+               valid_bits &= ~msk;
+               pvr2_ctrl_get_valname(cptr,msk,buf+bcnt,PAGE_SIZE-bcnt,&ccnt);
+               bcnt += ccnt;
+               if (bcnt >= PAGE_SIZE) break;
+               buf[bcnt] = '\n';
+               bcnt++;
+       }
+       pvr2_sysfs_trace("pvr2_sysfs(%p) show_bits(cid=%d)",sfp,id);
+       return bcnt;
+}
+
+static int store_val_any(int id,int customfl,struct pvr2_sysfs *sfp,
+                        const char *buf,unsigned int count)
+{
+       struct pvr2_ctrl *cptr;
+       int ret;
+       int mask,val;
+
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id);
+       if (customfl) {
+               ret = pvr2_ctrl_custom_sym_to_value(cptr,buf,count,&mask,&val);
+       } else {
+               ret = pvr2_ctrl_sym_to_value(cptr,buf,count,&mask,&val);
+       }
+       if (ret < 0) return ret;
+       ret = pvr2_ctrl_set_mask_value(cptr,mask,val);
+       pvr2_hdw_commit_ctl(sfp->channel.hdw);
+       return ret;
+}
+
+static ssize_t store_val_norm(int id,struct class_device *class_dev,
+                            const char *buf,size_t count)
+{
+       struct pvr2_sysfs *sfp;
+       int ret;
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       ret = store_val_any(id,0,sfp,buf,count);
+       if (!ret) ret = count;
+       return ret;
+}
+
+static ssize_t store_val_custom(int id,struct class_device *class_dev,
+                               const char *buf,size_t count)
+{
+       struct pvr2_sysfs *sfp;
+       int ret;
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       ret = store_val_any(id,1,sfp,buf,count);
+       if (!ret) ret = count;
+       return ret;
+}
+
+/*
+  Mike Isely <isely@pobox.com> 30-April-2005
+
+  This next batch of horrible preprocessor hackery is needed because the
+  kernel's class_device_attribute mechanism fails to pass the actual
+  attribute through to the show / store functions, which means we have no
+  way to package up any attribute-specific parameters, like for example the
+  control id.  So we work around this brain-damage by encoding the control
+  id into the show / store functions themselves and pick the function based
+  on the control id we're setting up.  These macros try to ease the pain.
+  Yuck.
+*/
+
+#define CREATE_SHOW_INSTANCE(sf_name,ctl_id) \
+static ssize_t sf_name##_##ctl_id(struct class_device *class_dev,char *buf) \
+{ return sf_name(ctl_id,class_dev,buf); }
+
+#define CREATE_STORE_INSTANCE(sf_name,ctl_id) \
+static ssize_t sf_name##_##ctl_id(struct class_device *class_dev,const char *buf,size_t count) \
+{ return sf_name(ctl_id,class_dev,buf,count); }
+
+#define CREATE_BATCH(ctl_id) \
+CREATE_SHOW_INSTANCE(show_name,ctl_id) \
+CREATE_SHOW_INSTANCE(show_min,ctl_id) \
+CREATE_SHOW_INSTANCE(show_max,ctl_id) \
+CREATE_SHOW_INSTANCE(show_val_norm,ctl_id) \
+CREATE_SHOW_INSTANCE(show_val_custom,ctl_id) \
+CREATE_SHOW_INSTANCE(show_enum,ctl_id) \
+CREATE_SHOW_INSTANCE(show_bits,ctl_id) \
+CREATE_STORE_INSTANCE(store_val_norm,ctl_id) \
+CREATE_STORE_INSTANCE(store_val_custom,ctl_id) \
+
+CREATE_BATCH(0)
+CREATE_BATCH(1)
+CREATE_BATCH(2)
+CREATE_BATCH(3)
+CREATE_BATCH(4)
+CREATE_BATCH(5)
+CREATE_BATCH(6)
+CREATE_BATCH(7)
+CREATE_BATCH(8)
+CREATE_BATCH(9)
+CREATE_BATCH(10)
+CREATE_BATCH(11)
+CREATE_BATCH(12)
+CREATE_BATCH(13)
+CREATE_BATCH(14)
+CREATE_BATCH(15)
+CREATE_BATCH(16)
+CREATE_BATCH(17)
+CREATE_BATCH(18)
+CREATE_BATCH(19)
+CREATE_BATCH(20)
+CREATE_BATCH(21)
+CREATE_BATCH(22)
+CREATE_BATCH(23)
+CREATE_BATCH(24)
+CREATE_BATCH(25)
+CREATE_BATCH(26)
+CREATE_BATCH(27)
+CREATE_BATCH(28)
+CREATE_BATCH(29)
+CREATE_BATCH(30)
+CREATE_BATCH(31)
+CREATE_BATCH(32)
+CREATE_BATCH(33)
+
+struct pvr2_sysfs_func_set {
+       ssize_t (*show_name)(struct class_device *,char *);
+       ssize_t (*show_min)(struct class_device *,char *);
+       ssize_t (*show_max)(struct class_device *,char *);
+       ssize_t (*show_enum)(struct class_device *,char *);
+       ssize_t (*show_bits)(struct class_device *,char *);
+       ssize_t (*show_val_norm)(struct class_device *,char *);
+       ssize_t (*store_val_norm)(struct class_device *,
+                                 const char *,size_t);
+       ssize_t (*show_val_custom)(struct class_device *,char *);
+       ssize_t (*store_val_custom)(struct class_device *,
+                                   const char *,size_t);
+};
+
+#define INIT_BATCH(ctl_id) \
+[ctl_id] = { \
+    .show_name = show_name_##ctl_id, \
+    .show_min = show_min_##ctl_id, \
+    .show_max = show_max_##ctl_id, \
+    .show_enum = show_enum_##ctl_id, \
+    .show_bits = show_bits_##ctl_id, \
+    .show_val_norm = show_val_norm_##ctl_id, \
+    .store_val_norm = store_val_norm_##ctl_id, \
+    .show_val_custom = show_val_custom_##ctl_id, \
+    .store_val_custom = store_val_custom_##ctl_id, \
+} \
+
+static struct pvr2_sysfs_func_set funcs[] = {
+       INIT_BATCH(0),
+       INIT_BATCH(1),
+       INIT_BATCH(2),
+       INIT_BATCH(3),
+       INIT_BATCH(4),
+       INIT_BATCH(5),
+       INIT_BATCH(6),
+       INIT_BATCH(7),
+       INIT_BATCH(8),
+       INIT_BATCH(9),
+       INIT_BATCH(10),
+       INIT_BATCH(11),
+       INIT_BATCH(12),
+       INIT_BATCH(13),
+       INIT_BATCH(14),
+       INIT_BATCH(15),
+       INIT_BATCH(16),
+       INIT_BATCH(17),
+       INIT_BATCH(18),
+       INIT_BATCH(19),
+       INIT_BATCH(20),
+       INIT_BATCH(21),
+       INIT_BATCH(22),
+       INIT_BATCH(23),
+       INIT_BATCH(24),
+       INIT_BATCH(25),
+       INIT_BATCH(26),
+       INIT_BATCH(27),
+       INIT_BATCH(28),
+       INIT_BATCH(29),
+       INIT_BATCH(30),
+       INIT_BATCH(31),
+       INIT_BATCH(32),
+       INIT_BATCH(33),
+};
+
+
+static void pvr2_sysfs_add_control(struct pvr2_sysfs *sfp,int ctl_id)
+{
+       struct pvr2_sysfs_ctl_item *cip;
+       struct pvr2_sysfs_func_set *fp;
+       struct pvr2_ctrl *cptr;
+       unsigned int cnt,acnt;
+
+       if ((ctl_id < 0) || (ctl_id >= (sizeof(funcs)/sizeof(funcs[0])))) {
+               return;
+       }
+
+       fp = funcs + ctl_id;
+       cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,ctl_id);
+       if (!cptr) return;
+
+       cip = kmalloc(sizeof(*cip),GFP_KERNEL);
+       if (!cip) return;
+       memset(cip,0,sizeof(*cip));
+       pvr2_sysfs_trace("Creating pvr2_sysfs_ctl_item id=%p",cip);
+
+       cip->cptr = cptr;
+
+       cip->chptr = sfp;
+       cip->item_next = 0;
+       if (sfp->item_last) {
+               sfp->item_last->item_next = cip;
+       } else {
+               sfp->item_first = cip;
+       }
+       sfp->item_last = cip;
+
+       cip->attr_name.attr.owner = THIS_MODULE;
+       cip->attr_name.attr.name = "name";
+       cip->attr_name.attr.mode = S_IRUGO;
+       cip->attr_name.show = fp->show_name;
+
+       cip->attr_min.attr.owner = THIS_MODULE;
+       cip->attr_min.attr.name = "min_val";
+       cip->attr_min.attr.mode = S_IRUGO;
+       cip->attr_min.show = fp->show_min;
+
+       cip->attr_max.attr.owner = THIS_MODULE;
+       cip->attr_max.attr.name = "max_val";
+       cip->attr_max.attr.mode = S_IRUGO;
+       cip->attr_max.show = fp->show_max;
+
+       cip->attr_val.attr.owner = THIS_MODULE;
+       cip->attr_val.attr.name = "cur_val";
+       cip->attr_val.attr.mode = S_IRUGO;
+
+       cip->attr_custom.attr.owner = THIS_MODULE;
+       cip->attr_custom.attr.name = "custom_val";
+       cip->attr_custom.attr.mode = S_IRUGO;
+
+       cip->attr_enum.attr.owner = THIS_MODULE;
+       cip->attr_enum.attr.name = "enum_val";
+       cip->attr_enum.attr.mode = S_IRUGO;
+       cip->attr_enum.show = fp->show_enum;
+
+       cip->attr_bits.attr.owner = THIS_MODULE;
+       cip->attr_bits.attr.name = "bit_val";
+       cip->attr_bits.attr.mode = S_IRUGO;
+       cip->attr_bits.show = fp->show_bits;
+
+       if (pvr2_ctrl_is_writable(cptr)) {
+               cip->attr_val.attr.mode |= S_IWUSR|S_IWGRP;
+               cip->attr_custom.attr.mode |= S_IWUSR|S_IWGRP;
+       }
+
+       acnt = 0;
+       cip->attr_gen[acnt++] = &cip->attr_name.attr;
+       cip->attr_gen[acnt++] = &cip->attr_val.attr;
+       cip->attr_val.show = fp->show_val_norm;
+       cip->attr_val.store = fp->store_val_norm;
+       if (pvr2_ctrl_has_custom_symbols(cptr)) {
+               cip->attr_gen[acnt++] = &cip->attr_custom.attr;
+               cip->attr_custom.show = fp->show_val_custom;
+               cip->attr_custom.store = fp->store_val_custom;
+       }
+       switch (pvr2_ctrl_get_type(cptr)) {
+       case pvr2_ctl_enum:
+               // Control is an enumeration
+               cip->attr_gen[acnt++] = &cip->attr_enum.attr;
+               break;
+       case pvr2_ctl_int:
+               // Control is an integer
+               cip->attr_gen[acnt++] = &cip->attr_min.attr;
+               cip->attr_gen[acnt++] = &cip->attr_max.attr;
+               break;
+       case pvr2_ctl_bitmask:
+               // Control is an bitmask
+               cip->attr_gen[acnt++] = &cip->attr_bits.attr;
+               break;
+       default: break;
+       }
+
+       cnt = scnprintf(cip->name,sizeof(cip->name)-1,"ctl_%s",
+                       pvr2_ctrl_get_name(cptr));
+       cip->name[cnt] = 0;
+       cip->grp.name = cip->name;
+       cip->grp.attrs = cip->attr_gen;
+
+       sysfs_create_group(&sfp->class_dev->kobj,&cip->grp);
+}
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+static ssize_t debuginfo_show(struct class_device *,char *);
+static ssize_t debugcmd_show(struct class_device *,char *);
+static ssize_t debugcmd_store(struct class_device *,const char *,size_t count);
+
+static void pvr2_sysfs_add_debugifc(struct pvr2_sysfs *sfp)
+{
+       struct pvr2_sysfs_debugifc *dip;
+       dip = kmalloc(sizeof(*dip),GFP_KERNEL);
+       if (!dip) return;
+       memset(dip,0,sizeof(*dip));
+       dip->attr_debugcmd.attr.owner = THIS_MODULE;
+       dip->attr_debugcmd.attr.name = "debugcmd";
+       dip->attr_debugcmd.attr.mode = S_IRUGO|S_IWUSR|S_IWGRP;
+       dip->attr_debugcmd.show = debugcmd_show;
+       dip->attr_debugcmd.store = debugcmd_store;
+       dip->attr_debuginfo.attr.owner = THIS_MODULE;
+       dip->attr_debuginfo.attr.name = "debuginfo";
+       dip->attr_debuginfo.attr.mode = S_IRUGO;
+       dip->attr_debuginfo.show = debuginfo_show;
+       sfp->debugifc = dip;
+       class_device_create_file(sfp->class_dev,&dip->attr_debugcmd);
+       class_device_create_file(sfp->class_dev,&dip->attr_debuginfo);
+}
+
+
+static void pvr2_sysfs_tear_down_debugifc(struct pvr2_sysfs *sfp)
+{
+       if (!sfp->debugifc) return;
+       class_device_remove_file(sfp->class_dev,
+                                &sfp->debugifc->attr_debuginfo);
+       class_device_remove_file(sfp->class_dev,&sfp->debugifc->attr_debugcmd);
+       kfree(sfp->debugifc);
+       sfp->debugifc = 0;
+}
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+
+static void pvr2_sysfs_add_controls(struct pvr2_sysfs *sfp)
+{
+       unsigned int idx,cnt;
+       cnt = pvr2_hdw_get_ctrl_count(sfp->channel.hdw);
+       for (idx = 0; idx < cnt; idx++) {
+               pvr2_sysfs_add_control(sfp,idx);
+       }
+}
+
+
+static void pvr2_sysfs_tear_down_controls(struct pvr2_sysfs *sfp)
+{
+       struct pvr2_sysfs_ctl_item *cip1,*cip2;
+       for (cip1 = sfp->item_first; cip1; cip1 = cip2) {
+               cip2 = cip1->item_next;
+               sysfs_remove_group(&sfp->class_dev->kobj,&cip1->grp);
+               pvr2_sysfs_trace("Destroying pvr2_sysfs_ctl_item id=%p",cip1);
+               kfree(cip1);
+       }
+}
+
+
+static void pvr2_sysfs_class_release(struct class *class)
+{
+       struct pvr2_sysfs_class *clp;
+       clp = container_of(class,struct pvr2_sysfs_class,class);
+       pvr2_sysfs_trace("Destroying pvr2_sysfs_class id=%p",clp);
+       kfree(clp);
+}
+
+
+static void pvr2_sysfs_release(struct class_device *class_dev)
+{
+       pvr2_sysfs_trace("Releasing class_dev id=%p",class_dev);
+       kfree(class_dev);
+}
+
+
+static void class_dev_destroy(struct pvr2_sysfs *sfp)
+{
+       if (!sfp->class_dev) return;
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+       pvr2_sysfs_tear_down_debugifc(sfp);
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+       pvr2_sysfs_tear_down_controls(sfp);
+       class_device_remove_file(sfp->class_dev,&sfp->attr_v4l_minor_number);
+       class_device_remove_file(sfp->class_dev,&sfp->attr_unit_number);
+       pvr2_sysfs_trace("Destroying class_dev id=%p",sfp->class_dev);
+       sfp->class_dev->class_data = 0;
+       class_device_unregister(sfp->class_dev);
+       sfp->class_dev = 0;
+}
+
+
+static ssize_t v4l_minor_number_show(struct class_device *class_dev,char *buf)
+{
+       struct pvr2_sysfs *sfp;
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       return scnprintf(buf,PAGE_SIZE,"%d\n",
+                        pvr2_hdw_v4l_get_minor_number(sfp->channel.hdw));
+}
+
+
+static ssize_t unit_number_show(struct class_device *class_dev,char *buf)
+{
+       struct pvr2_sysfs *sfp;
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       return scnprintf(buf,PAGE_SIZE,"%d\n",
+                        pvr2_hdw_get_unit_number(sfp->channel.hdw));
+}
+
+
+static void class_dev_create(struct pvr2_sysfs *sfp,
+                            struct pvr2_sysfs_class *class_ptr)
+{
+       struct usb_device *usb_dev;
+       struct class_device *class_dev;
+       usb_dev = pvr2_hdw_get_dev(sfp->channel.hdw);
+       if (!usb_dev) return;
+       class_dev = kmalloc(sizeof(*class_dev),GFP_KERNEL);
+       if (!class_dev) return;
+       memset(class_dev,0,sizeof(*class_dev));
+
+       pvr2_sysfs_trace("Creating class_dev id=%p",class_dev);
+
+       class_dev->class = &class_ptr->class;
+       if (pvr2_hdw_get_sn(sfp->channel.hdw)) {
+               snprintf(class_dev->class_id,BUS_ID_SIZE,"sn-%lu",
+                        pvr2_hdw_get_sn(sfp->channel.hdw));
+       } else if (pvr2_hdw_get_unit_number(sfp->channel.hdw) >= 0) {
+               snprintf(class_dev->class_id,BUS_ID_SIZE,"unit-%c",
+                        pvr2_hdw_get_unit_number(sfp->channel.hdw) + 'a');
+       } else {
+               kfree(class_dev);
+               return;
+       }
+
+       class_dev->dev = &usb_dev->dev;
+
+       sfp->class_dev = class_dev;
+       class_dev->class_data = sfp;
+       class_device_register(class_dev);
+
+       sfp->attr_v4l_minor_number.attr.owner = THIS_MODULE;
+       sfp->attr_v4l_minor_number.attr.name = "v4l_minor_number";
+       sfp->attr_v4l_minor_number.attr.mode = S_IRUGO;
+       sfp->attr_v4l_minor_number.show = v4l_minor_number_show;
+       sfp->attr_v4l_minor_number.store = 0;
+       class_device_create_file(sfp->class_dev,&sfp->attr_v4l_minor_number);
+       sfp->attr_unit_number.attr.owner = THIS_MODULE;
+       sfp->attr_unit_number.attr.name = "unit_number";
+       sfp->attr_unit_number.attr.mode = S_IRUGO;
+       sfp->attr_unit_number.show = unit_number_show;
+       sfp->attr_unit_number.store = 0;
+       class_device_create_file(sfp->class_dev,&sfp->attr_unit_number);
+
+       pvr2_sysfs_add_controls(sfp);
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+       pvr2_sysfs_add_debugifc(sfp);
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+}
+
+
+static void pvr2_sysfs_internal_check(struct pvr2_channel *chp)
+{
+       struct pvr2_sysfs *sfp;
+       sfp = container_of(chp,struct pvr2_sysfs,channel);
+       if (!sfp->channel.mc_head->disconnect_flag) return;
+       pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_sysfs id=%p",sfp);
+       class_dev_destroy(sfp);
+       pvr2_channel_done(&sfp->channel);
+       kfree(sfp);
+}
+
+
+struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *mp,
+                                    struct pvr2_sysfs_class *class_ptr)
+{
+       struct pvr2_sysfs *sfp;
+       sfp = kmalloc(sizeof(*sfp),GFP_KERNEL);
+       if (!sfp) return sfp;
+       memset(sfp,0,sizeof(*sfp));
+       pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_sysfs id=%p",sfp);
+       pvr2_channel_init(&sfp->channel,mp);
+       sfp->channel.check_func = pvr2_sysfs_internal_check;
+
+       class_dev_create(sfp,class_ptr);
+       return sfp;
+}
+
+
+static int pvr2_sysfs_hotplug(struct class_device *cd,char **envp,
+                             int numenvp,char *buf,int size)
+{
+       /* Even though we don't do anything here, we still need this function
+          because sysfs will still try to call it. */
+       return 0;
+}
+
+struct pvr2_sysfs_class *pvr2_sysfs_class_create(void)
+{
+       struct pvr2_sysfs_class *clp;
+       clp = kmalloc(sizeof(*clp),GFP_KERNEL);
+       if (!clp) return clp;
+       memset(clp,0,sizeof(*clp));
+       pvr2_sysfs_trace("Creating pvr2_sysfs_class id=%p",clp);
+       clp->class.name = "pvrusb2";
+       clp->class.class_release = pvr2_sysfs_class_release;
+       clp->class.release = pvr2_sysfs_release;
+       clp->class.uevent = pvr2_sysfs_hotplug;
+       if (class_register(&clp->class)) {
+               pvr2_sysfs_trace(
+                       "Registration failed for pvr2_sysfs_class id=%p",clp);
+               kfree(clp);
+               clp = 0;
+       }
+       return clp;
+}
+
+
+void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *clp)
+{
+       class_unregister(&clp->class);
+}
+
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+static ssize_t debuginfo_show(struct class_device *class_dev,char *buf)
+{
+       struct pvr2_sysfs *sfp;
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       pvr2_hdw_trigger_module_log(sfp->channel.hdw);
+       return pvr2_debugifc_print_info(sfp->channel.hdw,buf,PAGE_SIZE);
+}
+
+
+static ssize_t debugcmd_show(struct class_device *class_dev,char *buf)
+{
+       struct pvr2_sysfs *sfp;
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+       return pvr2_debugifc_print_status(sfp->channel.hdw,buf,PAGE_SIZE);
+}
+
+
+static ssize_t debugcmd_store(struct class_device *class_dev,
+                             const char *buf,size_t count)
+{
+       struct pvr2_sysfs *sfp;
+       int ret;
+
+       sfp = (struct pvr2_sysfs *)class_dev->class_data;
+       if (!sfp) return -EINVAL;
+
+       ret = pvr2_debugifc_docmd(sfp->channel.hdw,buf,count);
+       if (ret < 0) return ret;
+       return count;
+}
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-sysfs.h b/drivers/media/video/pvrusb2/pvrusb2-sysfs.h
new file mode 100644 (file)
index 0000000..ff9373b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_SYSFS_H
+#define __PVRUSB2_SYSFS_H
+
+#include <linux/list.h>
+#include <linux/sysfs.h>
+#include "pvrusb2-context.h"
+
+struct pvr2_sysfs;
+struct pvr2_sysfs_class;
+
+struct pvr2_sysfs_class *pvr2_sysfs_class_create(void);
+void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *);
+
+struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *,
+                                    struct pvr2_sysfs_class *);
+
+#endif /* __PVRUSB2_SYSFS_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-tuner.c b/drivers/media/video/pvrusb2/pvrusb2-tuner.c
new file mode 100644 (file)
index 0000000..f4aba81
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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.h"
+#include "pvrusb2-util.h"
+#include "pvrusb2-tuner.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+
+struct pvr2_tuner_handler {
+       struct pvr2_hdw *hdw;
+       struct pvr2_i2c_client *client;
+       struct pvr2_i2c_handler i2c_handler;
+       int type_update_fl;
+};
+
+
+static void set_type(struct pvr2_tuner_handler *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       struct tuner_setup setup;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c tuner set_type(%d)",hdw->tuner_type);
+       if (((int)(hdw->tuner_type)) < 0) return;
+
+       setup.addr = ADDR_UNSET;
+       setup.type = hdw->tuner_type;
+       setup.mode_mask = T_RADIO | T_ANALOG_TV;
+       /* We may really want mode_mask to be T_ANALOG_TV for now */
+       pvr2_i2c_client_cmd(ctxt->client,TUNER_SET_TYPE_ADDR,&setup);
+       ctxt->type_update_fl = 0;
+}
+
+
+static int tuner_check(struct pvr2_tuner_handler *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       if (hdw->tuner_updated) ctxt->type_update_fl = !0;
+       return ctxt->type_update_fl != 0;
+}
+
+
+static void tuner_update(struct pvr2_tuner_handler *ctxt)
+{
+       if (ctxt->type_update_fl) set_type(ctxt);
+}
+
+
+static void pvr2_tuner_detach(struct pvr2_tuner_handler *ctxt)
+{
+       ctxt->client->handler = 0;
+       kfree(ctxt);
+}
+
+
+static unsigned int pvr2_tuner_describe(struct pvr2_tuner_handler *ctxt,char *buf,unsigned int cnt)
+{
+       return scnprintf(buf,cnt,"handler: pvrusb2-tuner");
+}
+
+
+const static struct pvr2_i2c_handler_functions tuner_funcs = {
+       .detach = (void (*)(void *))pvr2_tuner_detach,
+       .check = (int (*)(void *))tuner_check,
+       .update = (void (*)(void *))tuner_update,
+       .describe = (unsigned int (*)(void *,char *,unsigned int))pvr2_tuner_describe,
+};
+
+
+int pvr2_i2c_tuner_setup(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp)
+{
+       struct pvr2_tuner_handler *ctxt;
+       if (cp->handler) return 0;
+
+       ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL);
+       if (!ctxt) return 0;
+       memset(ctxt,0,sizeof(*ctxt));
+
+       ctxt->i2c_handler.func_data = ctxt;
+       ctxt->i2c_handler.func_table = &tuner_funcs;
+       ctxt->type_update_fl = !0;
+       ctxt->client = cp;
+       ctxt->hdw = hdw;
+       cp->handler = &ctxt->i2c_handler;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x tuner handler set up",
+                  cp->client->addr);
+       return !0;
+}
+
+
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-tuner.h b/drivers/media/video/pvrusb2/pvrusb2-tuner.h
new file mode 100644 (file)
index 0000000..556f12a
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_TUNER_H
+#define __PVRUSB2_TUNER_H
+
+#include "pvrusb2-i2c-core.h"
+
+int pvr2_i2c_tuner_setup(struct pvr2_hdw *,struct pvr2_i2c_client *);
+
+#endif /* __PVRUSB2_TUNER_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-util.h b/drivers/media/video/pvrusb2/pvrusb2-util.h
new file mode 100644 (file)
index 0000000..e53aee4
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_UTIL_H
+#define __PVRUSB2_UTIL_H
+
+#define PVR2_DECOMPOSE_LE(t,i,d) \
+    do {    \
+       (t)[i] = (d) & 0xff;\
+       (t)[i+1] = ((d) >> 8) & 0xff;\
+       (t)[i+2] = ((d) >> 16) & 0xff;\
+       (t)[i+3] = ((d) >> 24) & 0xff;\
+    } while(0)
+
+#define PVR2_DECOMPOSE_BE(t,i,d) \
+    do {    \
+       (t)[i+3] = (d) & 0xff;\
+       (t)[i+2] = ((d) >> 8) & 0xff;\
+       (t)[i+1] = ((d) >> 16) & 0xff;\
+       (t)[i] = ((d) >> 24) & 0xff;\
+    } while(0)
+
+#define PVR2_COMPOSE_LE(t,i) \
+    ((((u32)((t)[i+3])) << 24) | \
+     (((u32)((t)[i+2])) << 16) | \
+     (((u32)((t)[i+1])) << 8) | \
+     ((u32)((t)[i])))
+
+#define PVR2_COMPOSE_BE(t,i) \
+    ((((u32)((t)[i])) << 24) | \
+     (((u32)((t)[i+1])) << 16) | \
+     (((u32)((t)[i+2])) << 8) | \
+     ((u32)((t)[i+3])))
+
+
+#endif /* __PVRUSB2_UTIL_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c
new file mode 100644 (file)
index 0000000..74b681a
--- /dev/null
@@ -0,0 +1,1056 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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 <linux/kernel.h>
+#include "pvrusb2-context.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-v4l2.h"
+#include "pvrusb2-ioread.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+
+struct pvr2_v4l2_dev;
+struct pvr2_v4l2_fh;
+struct pvr2_v4l2;
+
+/* V4L no longer provide the ability to set / get a private context pointer
+   (i.e. video_get_drvdata / video_set_drvdata), which means we have to
+   concoct our own context locating mechanism.  Supposedly this is intended
+   to simplify driver implementation.  It's not clear to me how that can
+   possibly be true.  Our solution here is to maintain a lookup table of
+   our context instances, indexed by the minor device number of the V4L
+   device.  See pvr2_v4l2_open() for some implications of this approach. */
+static struct pvr2_v4l2_dev *devices[256];
+static DEFINE_MUTEX(device_lock);
+
+struct pvr2_v4l2_dev {
+       struct pvr2_v4l2 *v4lp;
+       struct video_device *vdev;
+       struct pvr2_context_stream *stream;
+       int ctxt_idx;
+       enum pvr2_config config;
+};
+
+struct pvr2_v4l2_fh {
+       struct pvr2_channel channel;
+       struct pvr2_v4l2_dev *dev_info;
+       enum v4l2_priority prio;
+       struct pvr2_ioread *rhp;
+       struct file *file;
+       struct pvr2_v4l2 *vhead;
+       struct pvr2_v4l2_fh *vnext;
+       struct pvr2_v4l2_fh *vprev;
+       wait_queue_head_t wait_data;
+       int fw_mode_flag;
+};
+
+struct pvr2_v4l2 {
+       struct pvr2_channel channel;
+       struct pvr2_v4l2_fh *vfirst;
+       struct pvr2_v4l2_fh *vlast;
+
+       struct v4l2_prio_state prio;
+
+       /* streams */
+       struct pvr2_v4l2_dev video_dev;
+};
+
+static int video_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1};
+module_param_array(video_nr, int, NULL, 0444);
+MODULE_PARM_DESC(video_nr, "Offset for device's minor");
+
+struct v4l2_capability pvr_capability ={
+       .driver         = "pvrusb2",
+       .card           = "Hauppauge WinTV pvr-usb2",
+       .bus_info       = "usb",
+       .version        = KERNEL_VERSION(0,8,0),
+       .capabilities   = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE |
+                          V4L2_CAP_TUNER | V4L2_CAP_AUDIO |
+                          V4L2_CAP_READWRITE),
+       .reserved       = {0,0,0,0}
+};
+
+static struct v4l2_tuner pvr_v4l2_tuners[]= {
+       {
+               .index      = 0,
+               .name       = "TV Tuner",
+               .type           = V4L2_TUNER_ANALOG_TV,
+               .capability     = (V4L2_TUNER_CAP_NORM |
+                                  V4L2_TUNER_CAP_STEREO |
+                                  V4L2_TUNER_CAP_LANG1 |
+                                  V4L2_TUNER_CAP_LANG2),
+               .rangelow   = 0,
+               .rangehigh  = 0,
+               .rxsubchans     = V4L2_TUNER_SUB_STEREO,
+               .audmode        = V4L2_TUNER_MODE_STEREO,
+               .signal         = 0,
+               .afc            = 0,
+               .reserved       = {0,0,0,0}
+       }
+};
+
+struct v4l2_fmtdesc pvr_fmtdesc [] = {
+       {
+               .index          = 0,
+               .type           = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+               .flags          = V4L2_FMT_FLAG_COMPRESSED,
+               .description    = "MPEG1/2",
+               // This should really be V4L2_PIX_FMT_MPEG, but xawtv
+               // breaks when I do that.
+               .pixelformat    = 0, // V4L2_PIX_FMT_MPEG,
+               .reserved       = { 0, 0, 0, 0 }
+       }
+};
+
+#define PVR_FORMAT_PIX  0
+#define PVR_FORMAT_VBI  1
+
+struct v4l2_format pvr_format [] = {
+       [PVR_FORMAT_PIX] = {
+               .type   = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+               .fmt    = {
+                       .pix        = {
+                               .width          = 720,
+                               .height             = 576,
+                               // This should really be V4L2_PIX_FMT_MPEG,
+                               // but xawtv breaks when I do that.
+                               .pixelformat    = 0, // V4L2_PIX_FMT_MPEG,
+                               .field          = V4L2_FIELD_INTERLACED,
+                               .bytesperline   = 0,  // doesn't make sense
+                                                     // here
+                               //FIXME : Don't know what to put here...
+                               .sizeimage          = (32*1024),
+                               .colorspace     = 0, // doesn't make sense here
+                               .priv           = 0
+                       }
+               }
+       },
+       [PVR_FORMAT_VBI] = {
+               .type   = V4L2_BUF_TYPE_VBI_CAPTURE,
+               .fmt    = {
+                       .vbi        = {
+                               .sampling_rate = 27000000,
+                               .offset = 248,
+                               .samples_per_line = 1443,
+                               .sample_format = V4L2_PIX_FMT_GREY,
+                               .start = { 0, 0 },
+                               .count = { 0, 0 },
+                               .flags = 0,
+                               .reserved = { 0, 0 }
+                       }
+               }
+       }
+};
+
+/*
+ * pvr_ioctl()
+ *
+ * This is part of Video 4 Linux API. The procedure handles ioctl() calls.
+ *
+ */
+static int pvr2_v4l2_do_ioctl(struct inode *inode, struct file *file,
+                             unsigned int cmd, void *arg)
+{
+       struct pvr2_v4l2_fh *fh = file->private_data;
+       struct pvr2_v4l2 *vp = fh->vhead;
+       struct pvr2_v4l2_dev *dev_info = fh->dev_info;
+       struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+       int ret = -EINVAL;
+
+       if (pvrusb2_debug & PVR2_TRACE_V4LIOCTL) {
+               v4l_print_ioctl(pvr2_hdw_get_driver_name(hdw),cmd);
+       }
+
+       if (!pvr2_hdw_dev_ok(hdw)) {
+               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                          "ioctl failed - bad or no context");
+               return -EFAULT;
+       }
+
+       /* check priority */
+       switch (cmd) {
+       case VIDIOC_S_CTRL:
+       case VIDIOC_S_STD:
+       case VIDIOC_S_INPUT:
+       case VIDIOC_S_TUNER:
+       case VIDIOC_S_FREQUENCY:
+               ret = v4l2_prio_check(&vp->prio, &fh->prio);
+               if (ret)
+                       return ret;
+       }
+
+       switch (cmd) {
+       case VIDIOC_QUERYCAP:
+       {
+               struct v4l2_capability *cap = arg;
+
+               memcpy(cap, &pvr_capability, sizeof(struct v4l2_capability));
+
+               ret = 0;
+               break;
+       }
+
+       case VIDIOC_G_PRIORITY:
+       {
+               enum v4l2_priority *p = arg;
+
+               *p = v4l2_prio_max(&vp->prio);
+               ret = 0;
+               break;
+       }
+
+       case VIDIOC_S_PRIORITY:
+       {
+               enum v4l2_priority *prio = arg;
+
+               ret = v4l2_prio_change(&vp->prio, &fh->prio, *prio);
+               break;
+       }
+
+       case VIDIOC_ENUMSTD:
+       {
+               struct v4l2_standard *vs = (struct v4l2_standard *)arg;
+               int idx = vs->index;
+               ret = pvr2_hdw_get_stdenum_value(hdw,vs,idx+1);
+               break;
+       }
+
+       case VIDIOC_G_STD:
+       {
+               int val = 0;
+               ret = pvr2_ctrl_get_value(
+                       pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR),&val);
+               *(v4l2_std_id *)arg = val;
+               break;
+       }
+
+       case VIDIOC_S_STD:
+       {
+               ret = pvr2_ctrl_set_value(
+                       pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR),
+                       *(v4l2_std_id *)arg);
+               break;
+       }
+
+       case VIDIOC_ENUMINPUT:
+       {
+               struct pvr2_ctrl *cptr;
+               struct v4l2_input *vi = (struct v4l2_input *)arg;
+               struct v4l2_input tmp;
+               unsigned int cnt;
+
+               cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT);
+
+               memset(&tmp,0,sizeof(tmp));
+               tmp.index = vi->index;
+               ret = 0;
+               switch (vi->index) {
+               case PVR2_CVAL_INPUT_TV:
+               case PVR2_CVAL_INPUT_RADIO:
+                       tmp.type = V4L2_INPUT_TYPE_TUNER;
+                       break;
+               case PVR2_CVAL_INPUT_SVIDEO:
+               case PVR2_CVAL_INPUT_COMPOSITE:
+                       tmp.type = V4L2_INPUT_TYPE_CAMERA;
+                       break;
+               default:
+                       ret = -EINVAL;
+                       break;
+               }
+               if (ret < 0) break;
+
+               cnt = 0;
+               pvr2_ctrl_get_valname(cptr,vi->index,
+                                     tmp.name,sizeof(tmp.name)-1,&cnt);
+               tmp.name[cnt] = 0;
+
+               /* Don't bother with audioset, since this driver currently
+                  always switches the audio whenever the video is
+                  switched. */
+
+               /* Handling std is a tougher problem.  It doesn't make
+                  sense in cases where a device might be multi-standard.
+                  We could just copy out the current value for the
+                  standard, but it can change over time.  For now just
+                  leave it zero. */
+
+               memcpy(vi, &tmp, sizeof(tmp));
+
+               ret = 0;
+               break;
+       }
+
+       case VIDIOC_G_INPUT:
+       {
+               struct pvr2_ctrl *cptr;
+               struct v4l2_input *vi = (struct v4l2_input *)arg;
+               int val;
+               cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT);
+               val = 0;
+               ret = pvr2_ctrl_get_value(cptr,&val);
+               vi->index = val;
+               break;
+       }
+
+       case VIDIOC_S_INPUT:
+       {
+               struct v4l2_input *vi = (struct v4l2_input *)arg;
+               ret = pvr2_ctrl_set_value(
+                       pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT),
+                       vi->index);
+               break;
+       }
+
+       case VIDIOC_ENUMAUDIO:
+       {
+               ret = -EINVAL;
+               break;
+       }
+
+       case VIDIOC_G_AUDIO:
+       {
+               ret = -EINVAL;
+               break;
+       }
+
+       case VIDIOC_S_AUDIO:
+       {
+               ret = -EINVAL;
+               break;
+       }
+       case VIDIOC_G_TUNER:
+       {
+               struct v4l2_tuner *vt = (struct v4l2_tuner *)arg;
+               unsigned int status_mask;
+               int val;
+               if (vt->index !=0) break;
+
+               status_mask = pvr2_hdw_get_signal_status(hdw);
+
+               memcpy(vt, &pvr_v4l2_tuners[vt->index],
+                      sizeof(struct v4l2_tuner));
+
+               vt->signal = 0;
+               if (status_mask & PVR2_SIGNAL_OK) {
+                       if (status_mask & PVR2_SIGNAL_STEREO) {
+                               vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
+                       } else {
+                               vt->rxsubchans = V4L2_TUNER_SUB_MONO;
+                       }
+                       if (status_mask & PVR2_SIGNAL_SAP) {
+                               vt->rxsubchans |= (V4L2_TUNER_SUB_LANG1 |
+                                                  V4L2_TUNER_SUB_LANG2);
+                       }
+                       vt->signal = 65535;
+               }
+
+               val = 0;
+               ret = pvr2_ctrl_get_value(
+                       pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_AUDIOMODE),
+                       &val);
+               vt->audmode = val;
+               break;
+       }
+
+       case VIDIOC_S_TUNER:
+       {
+               struct v4l2_tuner *vt=(struct v4l2_tuner *)arg;
+
+               if (vt->index != 0)
+                       break;
+
+               ret = pvr2_ctrl_set_value(
+                       pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_AUDIOMODE),
+                       vt->audmode);
+       }
+
+       case VIDIOC_S_FREQUENCY:
+       {
+               const struct v4l2_frequency *vf = (struct v4l2_frequency *)arg;
+               ret = pvr2_ctrl_set_value(
+                       pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY),
+                       vf->frequency * 62500);
+               break;
+       }
+
+       case VIDIOC_G_FREQUENCY:
+       {
+               struct v4l2_frequency *vf = (struct v4l2_frequency *)arg;
+               int val = 0;
+               ret = pvr2_ctrl_get_value(
+                       pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY),
+                       &val);
+               val /= 62500;
+               vf->frequency = val;
+               break;
+       }
+
+       case VIDIOC_ENUM_FMT:
+       {
+               struct v4l2_fmtdesc *fd = (struct v4l2_fmtdesc *)arg;
+
+               /* Only one format is supported : mpeg.*/
+               if (fd->index != 0)
+                       break;
+
+               memcpy(fd, pvr_fmtdesc, sizeof(struct v4l2_fmtdesc));
+               ret = 0;
+               break;
+       }
+
+       case VIDIOC_G_FMT:
+       {
+               struct v4l2_format *vf = (struct v4l2_format *)arg;
+               int val;
+               switch(vf->type) {
+               case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+                       memcpy(vf, &pvr_format[PVR_FORMAT_PIX],
+                              sizeof(struct v4l2_format));
+                       val = 0;
+                       pvr2_ctrl_get_value(
+                               pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_HRES),
+                               &val);
+                       vf->fmt.pix.width = val;
+                       val = 0;
+                       pvr2_ctrl_get_value(
+                               pvr2_hdw_get_ctrl_by_id(hdw,
+                                                       PVR2_CID_INTERLACE),
+                               &val);
+                       if (val) vf->fmt.pix.width /= 2;
+                       val = 0;
+                       pvr2_ctrl_get_value(
+                               pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_VRES),
+                               &val);
+                       vf->fmt.pix.height = val;
+                       ret = 0;
+                       break;
+               case V4L2_BUF_TYPE_VBI_CAPTURE:
+                       // ????? Still need to figure out to do VBI correctly
+                       ret = -EINVAL;
+                       break;
+               default:
+                       ret = -EINVAL;
+                       break;
+               }
+               break;
+       }
+
+       case VIDIOC_TRY_FMT:
+       case VIDIOC_S_FMT:
+       {
+               struct v4l2_format *vf = (struct v4l2_format *)arg;
+
+               ret = 0;
+               switch(vf->type) {
+               case V4L2_BUF_TYPE_VIDEO_CAPTURE: {
+                       int h = vf->fmt.pix.height;
+                       int w = vf->fmt.pix.width;
+                       int vd_std, hf, hh;
+
+                       vd_std = 0;
+                       pvr2_ctrl_get_value(
+                               pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR),
+                               &vd_std);
+                       if (vd_std & V4L2_STD_525_60) {
+                               hf=480;
+                       } else {
+                               hf=576;
+                       }
+                       hh = (int) (hf / 2);
+
+                       memcpy(vf, &pvr_format[PVR_FORMAT_PIX],
+                              sizeof(struct v4l2_format));
+                       if (w > 720)
+                               vf->fmt.pix.width = 720;
+                       vf->fmt.pix.width &= 0xff0;
+                       vf->fmt.pix.height = (h > hh) ? hf : hh;
+
+                       if (cmd == VIDIOC_S_FMT) {
+                               pvr2_ctrl_set_value(
+                                       pvr2_hdw_get_ctrl_by_id(hdw,
+                                                               PVR2_CID_HRES),
+                                       vf->fmt.pix.width);
+                               pvr2_ctrl_set_value(
+                                       pvr2_hdw_get_ctrl_by_id(hdw,
+                                                               PVR2_CID_VRES),
+                                       vf->fmt.pix.height);
+                               pvr2_ctrl_set_value(
+                                       pvr2_hdw_get_ctrl_by_id(
+                                               hdw,PVR2_CID_INTERLACE),
+                                       vf->fmt.pix.height != hf);
+                       }
+               } break;
+               case V4L2_BUF_TYPE_VBI_CAPTURE:
+                       // ????? Still need to figure out to do VBI correctly
+                       ret = -EINVAL;
+                       break;
+               default:
+                       ret = -EINVAL;
+                       break;
+               }
+               break;
+       }
+
+       case VIDIOC_STREAMON:
+       {
+               ret = pvr2_hdw_set_stream_type(hdw,dev_info->config);
+               if (ret < 0) return ret;
+               ret = pvr2_hdw_set_streaming(hdw,!0);
+               break;
+       }
+
+       case VIDIOC_STREAMOFF:
+       {
+               ret = pvr2_hdw_set_streaming(hdw,0);
+               break;
+       }
+
+       case VIDIOC_QUERYCTRL:
+       {
+               struct pvr2_ctrl *cptr;
+               struct v4l2_queryctrl *vc = (struct v4l2_queryctrl *)arg;
+               ret = 0;
+               cptr = pvr2_hdw_get_ctrl_v4l(hdw,vc->id);
+               if (!cptr) {
+                       ret = -EINVAL;
+                       break;
+               }
+
+               strlcpy(vc->name,pvr2_ctrl_get_name(cptr),sizeof(vc->name));
+               vc->default_value = pvr2_ctrl_get_def(cptr);
+               switch (pvr2_ctrl_get_type(cptr)) {
+               case pvr2_ctl_enum:
+                       vc->type = V4L2_CTRL_TYPE_MENU;
+                       vc->minimum = 0;
+                       vc->maximum = pvr2_ctrl_get_cnt(cptr) - 1;
+                       vc->step = 1;
+                       break;
+               case pvr2_ctl_int:
+                       vc->type = V4L2_CTRL_TYPE_INTEGER;
+                       vc->minimum = pvr2_ctrl_get_min(cptr);
+                       vc->maximum = pvr2_ctrl_get_max(cptr);
+                       vc->step = 1;
+                       break;
+               default:
+                       ret = -EINVAL;
+                       break;
+               }
+               break;
+       }
+
+       case VIDIOC_QUERYMENU:
+       {
+               struct v4l2_querymenu *vm = (struct v4l2_querymenu *)arg;
+               unsigned int cnt = 0;
+               ret = pvr2_ctrl_get_valname(pvr2_hdw_get_ctrl_v4l(hdw,vm->id),
+                                           vm->index,
+                                           vm->name,sizeof(vm->name)-1,
+                                           &cnt);
+               vm->name[cnt] = 0;
+               break;
+       }
+
+       case VIDIOC_G_CTRL:
+       {
+               struct v4l2_control *vc = (struct v4l2_control *)arg;
+               int val = 0;
+               ret = pvr2_ctrl_get_value(pvr2_hdw_get_ctrl_v4l(hdw,vc->id),
+                                         &val);
+               vc->value = val;
+               break;
+       }
+
+       case VIDIOC_S_CTRL:
+       {
+               struct v4l2_control *vc = (struct v4l2_control *)arg;
+               ret = pvr2_ctrl_set_value(pvr2_hdw_get_ctrl_v4l(hdw,vc->id),
+                                         vc->value);
+               break;
+       }
+
+       case VIDIOC_LOG_STATUS:
+       {
+               int nr = pvr2_hdw_get_unit_number(hdw);
+
+               printk(KERN_INFO "pvrusb2: =================  START STATUS CARD #%d  =================\n", nr);
+               pvr2_hdw_trigger_module_log(hdw);
+               printk(KERN_INFO "pvrusb2: ==================  END STATUS CARD #%d  ==================\n", nr);
+               ret = 0;
+               break;
+       }
+
+       default :
+               ret = v4l_compat_translate_ioctl(inode,file,cmd,
+                                                arg,pvr2_v4l2_do_ioctl);
+       }
+
+       pvr2_hdw_commit_ctl(hdw);
+
+       if (ret < 0) {
+               if (pvrusb2_debug & PVR2_TRACE_V4LIOCTL) {
+                       pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                  "pvr2_v4l2_do_ioctl failure, ret=%d",ret);
+               } else {
+                       if (pvrusb2_debug & PVR2_TRACE_ERROR_LEGS) {
+                               pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+                                          "pvr2_v4l2_do_ioctl failure, ret=%d"
+                                          " command was:",ret);
+                               v4l_print_ioctl(pvr2_hdw_get_driver_name(hdw),
+                                               cmd);
+                       }
+               }
+       } else {
+               pvr2_trace(PVR2_TRACE_V4LIOCTL,
+                          "pvr2_v4l2_do_ioctl complete, ret=%d (0x%x)",
+                          ret,ret);
+       }
+       return ret;
+}
+
+
+static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip)
+{
+       pvr2_trace(PVR2_TRACE_INIT,
+                  "unregistering device video%d [%s]",
+                  dip->vdev->minor,pvr2_config_get_name(dip->config));
+       if (dip->ctxt_idx >= 0) {
+               mutex_lock(&device_lock);
+               devices[dip->ctxt_idx] = NULL;
+               dip->ctxt_idx = -1;
+               mutex_unlock(&device_lock);
+       }
+       video_unregister_device(dip->vdev);
+}
+
+
+static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp)
+{
+       pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw,-1);
+       pvr2_v4l2_dev_destroy(&vp->video_dev);
+
+       pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_v4l2 id=%p",vp);
+       pvr2_channel_done(&vp->channel);
+       kfree(vp);
+}
+
+
+void pvr2_v4l2_internal_check(struct pvr2_channel *chp)
+{
+       struct pvr2_v4l2 *vp;
+       vp = container_of(chp,struct pvr2_v4l2,channel);
+       if (!vp->channel.mc_head->disconnect_flag) return;
+       if (vp->vfirst) return;
+       pvr2_v4l2_destroy_no_lock(vp);
+}
+
+
+int pvr2_v4l2_ioctl(struct inode *inode, struct file *file,
+                   unsigned int cmd, unsigned long arg)
+{
+
+/* Temporary hack : use ivtv api until a v4l2 one is available. */
+#define IVTV_IOC_G_CODEC        0xFFEE7703
+#define IVTV_IOC_S_CODEC        0xFFEE7704
+       if (cmd == IVTV_IOC_G_CODEC || cmd == IVTV_IOC_S_CODEC) return 0;
+       return video_usercopy(inode, file, cmd, arg, pvr2_v4l2_do_ioctl);
+}
+
+
+int pvr2_v4l2_release(struct inode *inode, struct file *file)
+{
+       struct pvr2_v4l2_fh *fhp = file->private_data;
+       struct pvr2_v4l2 *vp = fhp->vhead;
+       struct pvr2_context *mp = fhp->vhead->channel.mc_head;
+
+       pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_release");
+
+       if (fhp->rhp) {
+               struct pvr2_stream *sp;
+               struct pvr2_hdw *hdw;
+               hdw = fhp->channel.mc_head->hdw;
+               pvr2_hdw_set_streaming(hdw,0);
+               sp = pvr2_ioread_get_stream(fhp->rhp);
+               if (sp) pvr2_stream_set_callback(sp,0,0);
+               pvr2_ioread_destroy(fhp->rhp);
+               fhp->rhp = 0;
+       }
+       v4l2_prio_close(&vp->prio, &fhp->prio);
+       file->private_data = NULL;
+
+       pvr2_context_enter(mp); do {
+               if (fhp->vnext) {
+                       fhp->vnext->vprev = fhp->vprev;
+               } else {
+                       vp->vlast = fhp->vprev;
+               }
+               if (fhp->vprev) {
+                       fhp->vprev->vnext = fhp->vnext;
+               } else {
+                       vp->vfirst = fhp->vnext;
+               }
+               fhp->vnext = 0;
+               fhp->vprev = 0;
+               fhp->vhead = 0;
+               pvr2_channel_done(&fhp->channel);
+               pvr2_trace(PVR2_TRACE_STRUCT,
+                          "Destroying pvr_v4l2_fh id=%p",fhp);
+               kfree(fhp);
+               if (vp->channel.mc_head->disconnect_flag && !vp->vfirst) {
+                       pvr2_v4l2_destroy_no_lock(vp);
+               }
+       } while (0); pvr2_context_exit(mp);
+       return 0;
+}
+
+
+int pvr2_v4l2_open(struct inode *inode, struct file *file)
+{
+       struct pvr2_v4l2_dev *dip = 0; /* Our own context pointer */
+       struct pvr2_v4l2_fh *fhp;
+       struct pvr2_v4l2 *vp;
+       struct pvr2_hdw *hdw;
+
+       mutex_lock(&device_lock);
+       /* MCI 7-Jun-2006 Even though we're just doing what amounts to an
+          atomic read of the device mapping array here, we still need the
+          mutex.  The problem is that there is a tiny race possible when
+          we register the device.  We can't update the device mapping
+          array until after the device has been registered, owing to the
+          fact that we can't know the minor device number until after the
+          registration succeeds.  And if another thread tries to open the
+          device in the window of time after registration but before the
+          map is updated, then it will get back an erroneous null pointer
+          and the open will result in a spurious failure.  The only way to
+          prevent that is to (a) be inside the mutex here before we access
+          the array, and (b) cover the entire registration process later
+          on with this same mutex.  Thus if we get inside the mutex here,
+          then we can be assured that the registration process actually
+          completed correctly.  This is an unhappy complication from the
+          use of global data in a driver that lives in a preemptible
+          environment.  It sure would be nice if the video device itself
+          had a means for storing and retrieving a local context pointer.
+          Oh wait.  It did.  But now it's gone.  Silly me. */
+       {
+               unsigned int midx = iminor(file->f_dentry->d_inode);
+               if (midx < sizeof(devices)/sizeof(devices[0])) {
+                       dip = devices[midx];
+               }
+       }
+       mutex_unlock(&device_lock);
+
+       if (!dip) return -ENODEV; /* Should be impossible but I'm paranoid */
+
+       vp = dip->v4lp;
+       hdw = vp->channel.hdw;
+
+       pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_open");
+
+       if (!pvr2_hdw_dev_ok(hdw)) {
+               pvr2_trace(PVR2_TRACE_OPEN_CLOSE,
+                          "pvr2_v4l2_open: hardware not ready");
+               return -EIO;
+       }
+
+       fhp = kmalloc(sizeof(*fhp),GFP_KERNEL);
+       if (!fhp) {
+               return -ENOMEM;
+       }
+       memset(fhp,0,sizeof(*fhp));
+
+       init_waitqueue_head(&fhp->wait_data);
+       fhp->dev_info = dip;
+
+       pvr2_context_enter(vp->channel.mc_head); do {
+               pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_v4l2_fh id=%p",fhp);
+               pvr2_channel_init(&fhp->channel,vp->channel.mc_head);
+               fhp->vnext = 0;
+               fhp->vprev = vp->vlast;
+               if (vp->vlast) {
+                       vp->vlast->vnext = fhp;
+               } else {
+                       vp->vfirst = fhp;
+               }
+               vp->vlast = fhp;
+               fhp->vhead = vp;
+       } while (0); pvr2_context_exit(vp->channel.mc_head);
+
+       fhp->file = file;
+       file->private_data = fhp;
+       v4l2_prio_open(&vp->prio,&fhp->prio);
+
+       fhp->fw_mode_flag = pvr2_hdw_cpufw_get_enabled(hdw);
+
+       return 0;
+}
+
+
+static void pvr2_v4l2_notify(struct pvr2_v4l2_fh *fhp)
+{
+       wake_up(&fhp->wait_data);
+}
+
+static int pvr2_v4l2_iosetup(struct pvr2_v4l2_fh *fh)
+{
+       int ret;
+       struct pvr2_stream *sp;
+       struct pvr2_hdw *hdw;
+       if (fh->rhp) return 0;
+
+       /* First read() attempt.  Try to claim the stream and start
+          it... */
+       if ((ret = pvr2_channel_claim_stream(&fh->channel,
+                                            fh->dev_info->stream)) != 0) {
+               /* Someone else must already have it */
+               return ret;
+       }
+
+       fh->rhp = pvr2_channel_create_mpeg_stream(fh->dev_info->stream);
+       if (!fh->rhp) {
+               pvr2_channel_claim_stream(&fh->channel,0);
+               return -ENOMEM;
+       }
+
+       hdw = fh->channel.mc_head->hdw;
+       sp = fh->dev_info->stream->stream;
+       pvr2_stream_set_callback(sp,(pvr2_stream_callback)pvr2_v4l2_notify,fh);
+       pvr2_hdw_set_stream_type(hdw,fh->dev_info->config);
+       pvr2_hdw_set_streaming(hdw,!0);
+       ret = pvr2_ioread_set_enabled(fh->rhp,!0);
+
+       return ret;
+}
+
+
+static ssize_t pvr2_v4l2_read(struct file *file,
+                             char __user *buff, size_t count, loff_t *ppos)
+{
+       struct pvr2_v4l2_fh *fh = file->private_data;
+       int ret;
+
+       if (fh->fw_mode_flag) {
+               struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+               char *tbuf;
+               int c1,c2;
+               int tcnt = 0;
+               unsigned int offs = *ppos;
+
+               tbuf = kmalloc(PAGE_SIZE,GFP_KERNEL);
+               if (!tbuf) return -ENOMEM;
+
+               while (count) {
+                       c1 = count;
+                       if (c1 > PAGE_SIZE) c1 = PAGE_SIZE;
+                       c2 = pvr2_hdw_cpufw_get(hdw,offs,tbuf,c1);
+                       if (c2 < 0) {
+                               tcnt = c2;
+                               break;
+                       }
+                       if (!c2) break;
+                       if (copy_to_user(buff,tbuf,c2)) {
+                               tcnt = -EFAULT;
+                               break;
+                       }
+                       offs += c2;
+                       tcnt += c2;
+                       buff += c2;
+                       count -= c2;
+                       *ppos += c2;
+               }
+               kfree(tbuf);
+               return tcnt;
+       }
+
+       if (!fh->rhp) {
+               ret = pvr2_v4l2_iosetup(fh);
+               if (ret) {
+                       return ret;
+               }
+       }
+
+       for (;;) {
+               ret = pvr2_ioread_read(fh->rhp,buff,count);
+               if (ret >= 0) break;
+               if (ret != -EAGAIN) break;
+               if (file->f_flags & O_NONBLOCK) break;
+               /* Doing blocking I/O.  Wait here. */
+               ret = wait_event_interruptible(
+                       fh->wait_data,
+                       pvr2_ioread_avail(fh->rhp) >= 0);
+               if (ret < 0) break;
+       }
+
+       return ret;
+}
+
+
+static unsigned int pvr2_v4l2_poll(struct file *file, poll_table *wait)
+{
+       unsigned int mask = 0;
+       struct pvr2_v4l2_fh *fh = file->private_data;
+       int ret;
+
+       if (fh->fw_mode_flag) {
+               mask |= POLLIN | POLLRDNORM;
+               return mask;
+       }
+
+       if (!fh->rhp) {
+               ret = pvr2_v4l2_iosetup(fh);
+               if (ret) return POLLERR;
+       }
+
+       poll_wait(file,&fh->wait_data,wait);
+
+       if (pvr2_ioread_avail(fh->rhp) >= 0) {
+               mask |= POLLIN | POLLRDNORM;
+       }
+
+       return mask;
+}
+
+
+static struct file_operations vdev_fops = {
+       .owner      = THIS_MODULE,
+       .open       = pvr2_v4l2_open,
+       .release    = pvr2_v4l2_release,
+       .read       = pvr2_v4l2_read,
+       .ioctl      = pvr2_v4l2_ioctl,
+       .llseek     = no_llseek,
+       .poll       = pvr2_v4l2_poll,
+};
+
+
+#define VID_HARDWARE_PVRUSB2    38  /* FIXME : need a good value */
+
+static struct video_device vdev_template = {
+       .owner      = THIS_MODULE,
+       .type       = VID_TYPE_CAPTURE | VID_TYPE_TUNER,
+       .type2      = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE
+                      | V4L2_CAP_TUNER | V4L2_CAP_AUDIO
+                      | V4L2_CAP_READWRITE),
+       .hardware   = VID_HARDWARE_PVRUSB2,
+       .fops       = &vdev_fops,
+};
+
+
+static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip,
+                              struct pvr2_v4l2 *vp,
+                              enum pvr2_config cfg)
+{
+       int mindevnum;
+       int unit_number;
+       int v4l_type;
+       dip->v4lp = vp;
+       dip->config = cfg;
+
+
+       switch (cfg) {
+       case pvr2_config_mpeg:
+               v4l_type = VFL_TYPE_GRABBER;
+               dip->stream = &vp->channel.mc_head->video_stream;
+               break;
+       case pvr2_config_vbi:
+               v4l_type = VFL_TYPE_VBI;
+               break;
+       case pvr2_config_radio:
+               v4l_type = VFL_TYPE_RADIO;
+               break;
+       default:
+               /* Bail out (this should be impossible) */
+               err("Failed to set up pvrusb2 v4l dev"
+                   " due to unrecognized config");
+               return;
+       }
+
+       if (!dip->stream) {
+               err("Failed to set up pvrusb2 v4l dev"
+                   " due to missing stream instance");
+               return;
+       }
+
+       dip->vdev = video_device_alloc();
+       if (!dip->vdev) {
+               err("Alloc of pvrusb2 v4l video device failed");
+               return;
+       }
+
+       memcpy(dip->vdev,&vdev_template,sizeof(vdev_template));
+       dip->vdev->release = video_device_release;
+       mutex_lock(&device_lock);
+
+       mindevnum = -1;
+       unit_number = pvr2_hdw_get_unit_number(vp->channel.mc_head->hdw);
+       if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+               mindevnum = video_nr[unit_number];
+       }
+       if ((video_register_device(dip->vdev, v4l_type, mindevnum) < 0) &&
+           (video_register_device(dip->vdev, v4l_type, -1) < 0)) {
+               err("Failed to register pvrusb2 v4l video device");
+       } else {
+               pvr2_trace(PVR2_TRACE_INIT,
+                          "registered device video%d [%s]",
+                          dip->vdev->minor,pvr2_config_get_name(dip->config));
+       }
+
+       if ((dip->vdev->minor < sizeof(devices)/sizeof(devices[0])) &&
+           (devices[dip->vdev->minor] == NULL)) {
+               dip->ctxt_idx = dip->vdev->minor;
+               devices[dip->ctxt_idx] = dip;
+       }
+       mutex_unlock(&device_lock);
+
+       pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw,
+                                       dip->vdev->minor);
+}
+
+
+struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *mnp)
+{
+       struct pvr2_v4l2 *vp;
+
+       vp = kmalloc(sizeof(*vp),GFP_KERNEL);
+       if (!vp) return vp;
+       memset(vp,0,sizeof(*vp));
+       vp->video_dev.ctxt_idx = -1;
+       pvr2_channel_init(&vp->channel,mnp);
+       pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp);
+
+       vp->channel.check_func = pvr2_v4l2_internal_check;
+
+       /* register streams */
+       pvr2_v4l2_dev_init(&vp->video_dev,vp,pvr2_config_mpeg);
+
+
+       return vp;
+}
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.h b/drivers/media/video/pvrusb2/pvrusb2-v4l2.h
new file mode 100644 (file)
index 0000000..9a995e2
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *
+ *  $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
+ *
+ */
+#ifndef __PVRUSB2_V4L2_H
+#define __PVRUSB2_V4L2_H
+
+#include "pvrusb2-context.h"
+
+struct pvr2_v4l2;
+
+struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *);
+
+#endif /* __PVRUSB2_V4L2_H */
+
+/*
+  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: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c b/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c
new file mode 100644 (file)
index 0000000..4127c82
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   saa711x support that is available in the v4l available starting
+   with linux 2.6.15.
+
+*/
+
+#include "pvrusb2-video-v4l.h"
+#include "pvrusb2-i2c-cmd-v4l2.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/saa7115.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+struct pvr2_v4l_decoder {
+       struct pvr2_i2c_handler handler;
+       struct pvr2_decoder_ctrl ctrl;
+       struct pvr2_i2c_client *client;
+       struct pvr2_hdw *hdw;
+       unsigned long stale_mask;
+};
+
+
+static void set_input(struct pvr2_v4l_decoder *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       struct v4l2_routing route;
+
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_input(%d)",hdw->input_val);
+       switch(hdw->input_val) {
+       case PVR2_CVAL_INPUT_TV:
+               route.input = SAA7115_COMPOSITE4;
+               break;
+       case PVR2_CVAL_INPUT_COMPOSITE:
+               route.input = SAA7115_COMPOSITE5;
+               break;
+       case PVR2_CVAL_INPUT_SVIDEO:
+               route.input = SAA7115_SVIDEO2;
+               break;
+       case PVR2_CVAL_INPUT_RADIO:
+               // ????? No idea yet what to do here
+       default:
+               return;
+       }
+       route.output = 0;
+       pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_VIDEO_ROUTING,&route);
+}
+
+
+static int check_input(struct pvr2_v4l_decoder *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       return hdw->input_dirty != 0;
+}
+
+
+static void set_audio(struct pvr2_v4l_decoder *ctxt)
+{
+       u32 val;
+       struct pvr2_hdw *hdw = ctxt->hdw;
+
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_audio %d",
+                  hdw->srate_val);
+       switch (hdw->srate_val) {
+       default:
+       case PVR2_CVAL_SRATE_48:
+               val = 48000;
+               break;
+       case PVR2_CVAL_SRATE_44_1:
+               val = 44100;
+               break;
+       }
+       pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_AUDIO_CLOCK_FREQ,&val);
+}
+
+
+static int check_audio(struct pvr2_v4l_decoder *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       return hdw->srate_dirty != 0;
+}
+
+
+struct pvr2_v4l_decoder_ops {
+       void (*update)(struct pvr2_v4l_decoder *);
+       int (*check)(struct pvr2_v4l_decoder *);
+};
+
+
+static const struct pvr2_v4l_decoder_ops decoder_ops[] = {
+       { .update = set_input, .check = check_input},
+       { .update = set_audio, .check = check_audio},
+};
+
+
+static void decoder_detach(struct pvr2_v4l_decoder *ctxt)
+{
+       ctxt->client->handler = 0;
+       ctxt->hdw->decoder_ctrl = 0;
+       kfree(ctxt);
+}
+
+
+static int decoder_check(struct pvr2_v4l_decoder *ctxt)
+{
+       unsigned long msk;
+       unsigned int idx;
+
+       for (idx = 0; idx < sizeof(decoder_ops)/sizeof(decoder_ops[0]);
+            idx++) {
+               msk = 1 << idx;
+               if (ctxt->stale_mask & msk) continue;
+               if (decoder_ops[idx].check(ctxt)) {
+                       ctxt->stale_mask |= msk;
+               }
+       }
+       return ctxt->stale_mask != 0;
+}
+
+
+static void decoder_update(struct pvr2_v4l_decoder *ctxt)
+{
+       unsigned long msk;
+       unsigned int idx;
+
+       for (idx = 0; idx < sizeof(decoder_ops)/sizeof(decoder_ops[0]);
+            idx++) {
+               msk = 1 << idx;
+               if (!(ctxt->stale_mask & msk)) continue;
+               ctxt->stale_mask &= ~msk;
+               decoder_ops[idx].update(ctxt);
+       }
+}
+
+
+static int decoder_detect(struct pvr2_i2c_client *cp)
+{
+       /* Attempt to query the decoder - let's see if it will answer */
+       struct v4l2_tuner vt;
+       int ret;
+
+       memset(&vt,0,sizeof(vt));
+       ret = pvr2_i2c_client_cmd(cp,VIDIOC_G_TUNER,&vt);
+       return ret == 0; /* Return true if it answered */
+}
+
+
+static void decoder_enable(struct pvr2_v4l_decoder *ctxt,int fl)
+{
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 decoder_enable(%d)",fl);
+       pvr2_v4l2_cmd_stream(ctxt->client,fl);
+}
+
+
+static int decoder_is_tuned(struct pvr2_v4l_decoder *ctxt)
+{
+       struct v4l2_tuner vt;
+       int ret;
+
+       memset(&vt,0,sizeof(vt));
+       ret = pvr2_i2c_client_cmd(ctxt->client,VIDIOC_G_TUNER,&vt);
+       if (ret < 0) return -EINVAL;
+       return vt.signal ? 1 : 0;
+}
+
+
+static unsigned int decoder_describe(struct pvr2_v4l_decoder *ctxt,char *buf,unsigned int cnt)
+{
+       return scnprintf(buf,cnt,"handler: pvrusb2-video-v4l");
+}
+
+
+const static struct pvr2_i2c_handler_functions hfuncs = {
+       .detach = (void (*)(void *))decoder_detach,
+       .check = (int (*)(void *))decoder_check,
+       .update = (void (*)(void *))decoder_update,
+       .describe = (unsigned int (*)(void *,char *,unsigned int))decoder_describe,
+};
+
+
+int pvr2_i2c_decoder_v4l_setup(struct pvr2_hdw *hdw,
+                              struct pvr2_i2c_client *cp)
+{
+       struct pvr2_v4l_decoder *ctxt;
+
+       if (hdw->decoder_ctrl) return 0;
+       if (cp->handler) return 0;
+       if (!decoder_detect(cp)) return 0;
+
+       ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL);
+       if (!ctxt) return 0;
+       memset(ctxt,0,sizeof(*ctxt));
+
+       ctxt->handler.func_data = ctxt;
+       ctxt->handler.func_table = &hfuncs;
+       ctxt->ctrl.ctxt = ctxt;
+       ctxt->ctrl.detach = (void (*)(void *))decoder_detach;
+       ctxt->ctrl.enable = (void (*)(void *,int))decoder_enable;
+       ctxt->ctrl.tuned = (int (*)(void *))decoder_is_tuned;
+       ctxt->client = cp;
+       ctxt->hdw = hdw;
+       ctxt->stale_mask = (1 << (sizeof(decoder_ops)/
+                                 sizeof(decoder_ops[0]))) - 1;
+       hdw->decoder_ctrl = &ctxt->ctrl;
+       cp->handler = &ctxt->handler;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x saa711x V4L2 handler set up",
+                  cp->client->addr);
+       return !0;
+}
+
+
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h b/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h
new file mode 100644 (file)
index 0000000..2b917fd
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+#ifndef __PVRUSB2_VIDEO_V4L_H
+#define __PVRUSB2_VIDEO_V4L_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which handles device video processing.  This interface is
+   used internally by the driver; higher level code should only
+   interact through the interface provided by pvrusb2-hdw.h.
+
+*/
+
+
+
+#include "pvrusb2-i2c-core.h"
+
+int pvr2_i2c_decoder_v4l_setup(struct pvr2_hdw *,struct pvr2_i2c_client *);
+
+
+#endif /* __PVRUSB2_VIDEO_V4L_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-wm8775.c b/drivers/media/video/pvrusb2/pvrusb2-wm8775.c
new file mode 100644 (file)
index 0000000..fcad346
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   wm8775.
+
+*/
+
+#include "pvrusb2-wm8775.h"
+#include "pvrusb2-i2c-cmd-v4l2.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+struct pvr2_v4l_wm8775 {
+       struct pvr2_i2c_handler handler;
+       struct pvr2_i2c_client *client;
+       struct pvr2_hdw *hdw;
+       unsigned long stale_mask;
+};
+
+
+static void set_input(struct pvr2_v4l_wm8775 *ctxt)
+{
+       struct v4l2_routing route;
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       int msk = 0;
+
+       memset(&route,0,sizeof(route));
+
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c wm8775 set_input(val=%d msk=0x%x)",
+                  hdw->input_val,msk);
+
+       // Always point to input #1 no matter what
+       route.input = 2;
+       pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_AUDIO_ROUTING,&route);
+}
+
+static int check_input(struct pvr2_v4l_wm8775 *ctxt)
+{
+       struct pvr2_hdw *hdw = ctxt->hdw;
+       return hdw->input_dirty != 0;
+}
+
+
+struct pvr2_v4l_wm8775_ops {
+       void (*update)(struct pvr2_v4l_wm8775 *);
+       int (*check)(struct pvr2_v4l_wm8775 *);
+};
+
+
+static const struct pvr2_v4l_wm8775_ops wm8775_ops[] = {
+       { .update = set_input, .check = check_input},
+};
+
+
+static unsigned int wm8775_describe(struct pvr2_v4l_wm8775 *ctxt,
+                                    char *buf,unsigned int cnt)
+{
+       return scnprintf(buf,cnt,"handler: pvrusb2-wm8775");
+}
+
+
+static void wm8775_detach(struct pvr2_v4l_wm8775 *ctxt)
+{
+       ctxt->client->handler = 0;
+       kfree(ctxt);
+}
+
+
+static int wm8775_check(struct pvr2_v4l_wm8775 *ctxt)
+{
+       unsigned long msk;
+       unsigned int idx;
+
+       for (idx = 0; idx < sizeof(wm8775_ops)/sizeof(wm8775_ops[0]);
+            idx++) {
+               msk = 1 << idx;
+               if (ctxt->stale_mask & msk) continue;
+               if (wm8775_ops[idx].check(ctxt)) {
+                       ctxt->stale_mask |= msk;
+               }
+       }
+       return ctxt->stale_mask != 0;
+}
+
+
+static void wm8775_update(struct pvr2_v4l_wm8775 *ctxt)
+{
+       unsigned long msk;
+       unsigned int idx;
+
+       for (idx = 0; idx < sizeof(wm8775_ops)/sizeof(wm8775_ops[0]);
+            idx++) {
+               msk = 1 << idx;
+               if (!(ctxt->stale_mask & msk)) continue;
+               ctxt->stale_mask &= ~msk;
+               wm8775_ops[idx].update(ctxt);
+       }
+}
+
+
+const static struct pvr2_i2c_handler_functions hfuncs = {
+       .detach = (void (*)(void *))wm8775_detach,
+       .check = (int (*)(void *))wm8775_check,
+       .update = (void (*)(void *))wm8775_update,
+       .describe = (unsigned int (*)(void *,char *,unsigned int))wm8775_describe,
+};
+
+
+int pvr2_i2c_wm8775_setup(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp)
+{
+       struct pvr2_v4l_wm8775 *ctxt;
+
+       if (cp->handler) return 0;
+
+       ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL);
+       if (!ctxt) return 0;
+       memset(ctxt,0,sizeof(*ctxt));
+
+       ctxt->handler.func_data = ctxt;
+       ctxt->handler.func_table = &hfuncs;
+       ctxt->client = cp;
+       ctxt->hdw = hdw;
+       ctxt->stale_mask = (1 << (sizeof(wm8775_ops)/
+                                 sizeof(wm8775_ops[0]))) - 1;
+       cp->handler = &ctxt->handler;
+       pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x wm8775 V4L2 handler set up",
+                  cp->client->addr);
+       return !0;
+}
+
+
+
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2-wm8775.h b/drivers/media/video/pvrusb2/pvrusb2-wm8775.h
new file mode 100644 (file)
index 0000000..8aaeff4
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+#ifndef __PVRUSB2_WM8775_H
+#define __PVRUSB2_WM8775_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which performs analog -> digital audio conversion for
+   external audio inputs.  This interface is used internally by the
+   driver; higher level code should only interact through the
+   interface provided by pvrusb2-hdw.h.
+
+*/
+
+
+
+#include "pvrusb2-i2c-core.h"
+
+int pvr2_i2c_wm8775_setup(struct pvr2_hdw *,struct pvr2_i2c_client *);
+
+
+#endif /* __PVRUSB2_WM8775_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */
diff --git a/drivers/media/video/pvrusb2/pvrusb2.h b/drivers/media/video/pvrusb2/pvrusb2.h
new file mode 100644 (file)
index 0000000..074533e
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ */
+
+#ifndef __PVRUSB2_H
+#define __PVRUSB2_H
+
+/* Maximum number of pvrusb2 instances we can track at once.  You
+   might want to increase this - however the driver operation will not
+   be impaired if it is too small.  Instead additional units just
+   won't have an ID assigned and it might not be possible to specify
+   module paramters for those extra units. */
+#define PVR_NUM 20
+
+#endif /* __PVRUSB2_H */
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 70 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */