From 624296384671a4a4385b3486815ed4213d82a5cb Mon Sep 17 00:00:00 2001 From: Michael Krufky Date: Sun, 26 Feb 2006 00:51:53 -0400 Subject: add linux/Documentation/video4linux/README.pvrusb2 From: Michael Krufky - moved out of v4l_experimental Signed-off-by: Michael Krufky --- linux/Documentation/video4linux/README.pvrusb2 | 212 +++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 linux/Documentation/video4linux/README.pvrusb2 (limited to 'linux') diff --git a/linux/Documentation/video4linux/README.pvrusb2 b/linux/Documentation/video4linux/README.pvrusb2 new file mode 100644 index 000000000..c73a32c34 --- /dev/null +++ b/linux/Documentation/video4linux/README.pvrusb2 @@ -0,0 +1,212 @@ + +$Id$ +Mike Isely + + 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 whose web page can be found here: + + http://pvrusb2.dax.nu/ + + From there Aurelien Alleaume 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 + -- cgit v1.2.3 From 95b46b95fbbd8226ebaccd556a541aeb061aa980 Mon Sep 17 00:00:00 2001 From: Michael Krufky Date: Sun, 26 Feb 2006 00:55:22 -0400 Subject: moved pvrusb2 driver into the kernel tree From: Michael Krufky Signed-off-by: Michael Krufky --- linux/drivers/media/video/pvrusb2/pvrusb2-audio.c | 260 +++ linux/drivers/media/video/pvrusb2/pvrusb2-audio.h | 40 + .../drivers/media/video/pvrusb2/pvrusb2-context.c | 212 ++ .../drivers/media/video/pvrusb2/pvrusb2-context.h | 98 + linux/drivers/media/video/pvrusb2/pvrusb2-debug.h | 67 + .../drivers/media/video/pvrusb2/pvrusb2-debugifc.c | 476 ++++ .../drivers/media/video/pvrusb2/pvrusb2-debugifc.h | 53 + linux/drivers/media/video/pvrusb2/pvrusb2-demod.c | 127 + linux/drivers/media/video/pvrusb2/pvrusb2-demod.h | 38 + linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.c | 275 +++ linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.h | 41 + .../drivers/media/video/pvrusb2/pvrusb2-encoder.c | 494 ++++ .../drivers/media/video/pvrusb2/pvrusb2-encoder.h | 42 + .../media/video/pvrusb2/pvrusb2-hdw-internal.h | 224 ++ linux/drivers/media/video/pvrusb2/pvrusb2-hdw.c | 2430 ++++++++++++++++++++ linux/drivers/media/video/pvrusb2/pvrusb2-hdw.h | 412 ++++ .../media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c | 102 + .../media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c | 250 ++ .../media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h | 46 + .../drivers/media/video/pvrusb2/pvrusb2-i2c-core.c | 765 ++++++ .../drivers/media/video/pvrusb2/pvrusb2-i2c-core.h | 96 + linux/drivers/media/video/pvrusb2/pvrusb2-io.c | 716 ++++++ linux/drivers/media/video/pvrusb2/pvrusb2-io.h | 102 + linux/drivers/media/video/pvrusb2/pvrusb2-ioread.c | 367 +++ linux/drivers/media/video/pvrusb2/pvrusb2-ioread.h | 47 + linux/drivers/media/video/pvrusb2/pvrusb2-main.c | 183 ++ linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.c | 806 +++++++ linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.h | 47 + linux/drivers/media/video/pvrusb2/pvrusb2-tuner.c | 123 + linux/drivers/media/video/pvrusb2/pvrusb2-tuner.h | 38 + linux/drivers/media/video/pvrusb2/pvrusb2-util.h | 63 + linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c | 1270 ++++++++++ linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.h | 40 + .../media/video/pvrusb2/pvrusb2-video-v4l.c | 248 ++ .../media/video/pvrusb2/pvrusb2-video-v4l.h | 53 + linux/drivers/media/video/pvrusb2/pvrusb2.h | 43 + 36 files changed, 10694 insertions(+) create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-audio.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-audio.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-context.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-context.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-debug.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-debugifc.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-debugifc.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-demod.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-demod.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-encoder.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-encoder.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-hdw.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-hdw.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-io.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-io.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-ioread.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-ioread.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-main.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-tuner.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-tuner.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-util.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h create mode 100644 linux/drivers/media/video/pvrusb2/pvrusb2.h (limited to 'linux') diff --git a/linux/drivers/media/video/pvrusb2/pvrusb2-audio.c b/linux/drivers/media/video/pvrusb2/pvrusb2-audio.c new file mode 100644 index 000000000..7e2fab330 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-audio.c @@ -0,0 +1,260 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 "compat.h" +#include "pvrusb2-audio.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include "msp3400.h" +#include +#include +#include + +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; +}; + + +/* + + MCI 10-Mar-2005 - Rather than operate the msp34xx + directly, we rely on the msp3400.ko module to do it for us. We + really have to do this because that $##@!! module is going to attach + itself to us anyway, so we really can't operate the chip ourselves. + Unfortunately msp3400.ko is a real train wreck of a piece of code. + Most of the code below tries to tickle that module in just the right + way to get the results we need. Yuck. msp3400.c should be taken + out back and shot. Based on my reading of the actual chip datasheet + it should in theory be possible to write a far cleaner and simpler + driver than what is currently there right now. + +*/ + +static int xlat_audiomode_to_v4l2(int id) +{ + switch (id) { + case PVR2_CVAL_AUDIOMODE_MONO: + return V4L2_TUNER_MODE_MONO; + case PVR2_CVAL_AUDIOMODE_STEREO: + return V4L2_TUNER_MODE_STEREO; + case PVR2_CVAL_AUDIOMODE_SAP: + return V4L2_TUNER_MODE_SAP; + case PVR2_CVAL_AUDIOMODE_LANG1: + return V4L2_TUNER_MODE_LANG1; + case PVR2_CVAL_AUDIOMODE_LANG2: + return V4L2_TUNER_MODE_LANG2; + } + return V4L2_TUNER_MODE_STEREO; +} + + +/* This function selects the correct audio input source */ +static void set_stereo(struct pvr2_msp3400_handler *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + unsigned short sarg = 0; + struct msp_matrix mspm; + + pvr2_trace(PVR2_TRACE_CHIPS,"i2c msp3400 set_stereo"); + + if (hdw->controls[PVR2_CID_INPUT].value == PVR2_CVAL_INPUT_TV) { + struct v4l2_tuner vt; + memset(&vt,0,sizeof(vt)); + vt.audmode = xlat_audiomode_to_v4l2( + hdw->controls[PVR2_CID_AUDIOMODE].value); + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_S_TUNER,&vt); + } + + sarg = AUDIO_TUNER; + switch (hdw->controls[PVR2_CID_INPUT].value) { + case PVR2_CVAL_INPUT_TV: + sarg = AUDIO_TUNER; + break; + case PVR2_CVAL_INPUT_RADIO: + /* Assume that msp34xx also handle FM decoding, in which case + we're still using the tuner. */ + sarg = AUDIO_TUNER; + break; + case PVR2_CVAL_INPUT_SVIDEO: + case PVR2_CVAL_INPUT_COMPOSITE: + sarg = AUDIO_EXTERN; + break; + } + pvr2_i2c_client_cmd(ctxt->client,AUDC_SET_INPUT,&sarg); + + /* The above should have been enough to do the job, however + msp3400.ko does an incomplete job of handling the scart + routing. Really. It doesn't even bother to initialize the + SC1 output at all. So we have to help it here... + Unfortunately this also means that we have include + msp3400.c's header file and it currently isn't in a public + place. Damnit! */ + + mspm.input = SCART_IN1_DA; + switch (hdw->controls[PVR2_CID_INPUT].value) { + case PVR2_CVAL_INPUT_SVIDEO: + case PVR2_CVAL_INPUT_COMPOSITE: + /* Bypass the DSP and just use IN1. In theory we + should be able to permanent just use IN1_DA, but to + do that msp3400.ko should be adjusting the DSP + input SCART routing correctly when doing video in. + Unfortunately that appears not to be the case. I + really hate that module. */ + mspm.input = SCART_IN1; + break; + } + mspm.output = 1; + pvr2_i2c_client_cmd(ctxt->client,MSP_SET_MATRIX,&mspm); +} + + +static int check_stereo(struct pvr2_msp3400_handler *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + return ((hdw->controls[PVR2_CID_INPUT].dirty != 0)|| + (hdw->controls[PVR2_CID_AUDIOMODE].dirty != 0)); +} + + +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 volume parameters and signal type */ +static int get_audio_status(struct pvr2_msp3400_handler *ctxt) +{ + struct video_audio vt; + int stat; + + memset(&vt,0,sizeof(vt)); + stat = pvr2_i2c_client_cmd(ctxt->client,VIDIOCGAUDIO,&vt); + if (stat < 0) return stat; + + ctxt->hdw->flag_stereo = (vt.mode & VIDEO_SOUND_STEREO) != 0; + ctxt->hdw->flag_bilingual = + (vt.mode & (VIDEO_SOUND_LANG1|VIDEO_SOUND_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"); +} + + +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 V4L1 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/linux/drivers/media/video/pvrusb2/pvrusb2-audio.h b/linux/drivers/media/video/pvrusb2/pvrusb2-audio.h new file mode 100644 index 000000000..536339b68 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-audio.h @@ -0,0 +1,40 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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/linux/drivers/media/video/pvrusb2/pvrusb2-context.c b/linux/drivers/media/video/pvrusb2/pvrusb2-context.c new file mode 100644 index 000000000..b8f7fb043 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-context.c @@ -0,0 +1,212 @@ +/* + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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-hdw.h" +#include "pvrusb2-debug.h" +#include +#include +#include +#include + + +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_trace(PVR2_TRACE_DEBUG,"pvr2_context_poll BEGIN"); + pvr2_context_enter(mp); do { + pvr2_hdw_poll(mp->hdw); + } while (0); pvr2_context_exit(mp); + pvr2_trace(PVR2_TRACE_DEBUG,"pvr2_context_poll END"); +} + + +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,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); + 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; +} + + +/* + 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/linux/drivers/media/video/pvrusb2/pvrusb2-context.h b/linux/drivers/media/video/pvrusb2/pvrusb2-context.h new file mode 100644 index 000000000..e721cc559 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-context.h @@ -0,0 +1,98 @@ +/* + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 "compat.h" +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) +#include +#include +#else +#include +#endif +#include +#include + +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_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; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) + struct mutex mutex; +#else + struct semaphore mutex; +#endif + 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, + 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 *); + + +#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/linux/drivers/media/video/pvrusb2/pvrusb2-debug.h b/linux/drivers/media/video/pvrusb2/pvrusb2-debug.h new file mode 100644 index 000000000..078f5f5a0 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-debug.h @@ -0,0 +1,67 @@ +/* + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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/linux/drivers/media/video/pvrusb2/pvrusb2-debugifc.c b/linux/drivers/media/video/pvrusb2/pvrusb2-debugifc.c new file mode 100644 index 000000000..bcfe468eb --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-debugifc.c @@ -0,0 +1,476 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 +#include +#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",PVR2_SUBSYS_ENC_FIRMWARE}, + {"ENC_CFG",PVR2_SUBSYS_ENC_CFG}, + {"DIG_RUN",PVR2_SUBSYS_DIGITIZER_RUN}, + {"USB_RUN",PVR2_SUBSYS_USBSTREAM_RUN}, + {"ENC_RUN",PVR2_SUBSYS_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_soft_reset(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); + } + 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/linux/drivers/media/video/pvrusb2/pvrusb2-debugifc.h b/linux/drivers/media/video/pvrusb2/pvrusb2-debugifc.h new file mode 100644 index 000000000..990b02d35 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-debugifc.h @@ -0,0 +1,53 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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/linux/drivers/media/video/pvrusb2/pvrusb2-demod.c b/linux/drivers/media/video/pvrusb2/pvrusb2-demod.c new file mode 100644 index 000000000..dca787dfa --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-demod.c @@ -0,0 +1,127 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 "compat.h" +#include +#include +#include + + +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/linux/drivers/media/video/pvrusb2/pvrusb2-demod.h b/linux/drivers/media/video/pvrusb2/pvrusb2-demod.h new file mode 100644 index 000000000..4c4e40ffb --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-demod.h @@ -0,0 +1,38 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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/linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.c b/linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.c new file mode 100644 index 000000000..60ee45ca2 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.c @@ -0,0 +1,275 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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" +#include "compat.h" + +#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__) + +/* + + isely@pobox.com 16-Oct-2005 - There are two method by which we can + theoretically retrieve information from the device's eeprom: + + Method #1: We expect tveeprom to attach to our I2C adapter as a + client, in which case we send it a command to tell us what it + knows about the device. This is the "indirect" method. + + Method #2: We retrieve the eeprom contents ourselves and call into + tveeprom_hauppauge_analog() to parse the data and tell us what + it knows about the device. This is the "direct" method. + + Unfortunately it isn't perfectly clear which method is the best. + They each have pros and cons: + + #1 is simpler & more portable and has an API which is more stable. + + #1 doesn't provide as much information as #2 does. For example, we + can't retrieve the device's serial number with method #1. + + #1 requires that tveeprom.ko autonomously detect the eeprom chip on + its own; we can't help it out here. Worse still, it seems that + the eeprom in some PVR USB2 devices (like mine) can't be detected + correctly (I don't see an ack on a zero length write which is + what the I2C core attempts). + + #2 uses an unstable API. Current the ivtv implementation of #2 uses + a completely different tveeprom struct than the v4l + implementation of #2. This causes a usability nightmare. + + Since I can't decide, both methods are implemented below. Method #2 + (direct) is the default choice, but if you want to try method #1, + then define PVR2_EEPROM_INDIRECT and cross your fingers... + + If you use method #1, please be aware that you won't have a serial + number for the device and thus the sysfs interface may be a little + different. In addition, if tveeprom.ko fails to detect the eeprom + you may have to force it using standard i2c module options (try + force=-1,80). FINALLY (and this may foreclose this option for you + completely), the PVR USB2 eeprom seems to have valid data only in + the upper 128 bytes - the lower 128 bytes causes tveeprom.ko to + abort. In method #2 we only read the upper 128 bytes... + + */ + + + + +/* Stuff common to direct approach of operation tveeprom */ + +/* + + 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 + +/* 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 bit 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 = hdw->eeprom_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; +} + + +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ +/* BEGIN DIRECT METHOD, V4L ONLY */ + + +/* Directly call eeprom analysis function within tveeprom. This + version directly assumes it is talking to the V4L version of + tveeprom.ko and does not attempt anything ugly to maintain + backwards compatibility. */ + +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->video_standards = tvdata.tuner_formats; + + kfree(eeprom); + + return 0; +} + + + +/* END DIRECT METHOD, V4L ONLY */ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + + + + + + +static v4l2_std_id std_choices[] = { + [PVR2_CVAL_VIDEOSTANDARD_NTSC_M] = V4L2_STD_NTSC_M, + [PVR2_CVAL_VIDEOSTANDARD_PAL_BG] = V4L2_STD_PAL_BG, + [PVR2_CVAL_VIDEOSTANDARD_PAL_I] = V4L2_STD_PAL_I, + [PVR2_CVAL_VIDEOSTANDARD_PAL_DK] = V4L2_STD_PAL_DK, + [PVR2_CVAL_VIDEOSTANDARD_SECAM_L] = V4L2_STD_SECAM_L, + [PVR2_CVAL_VIDEOSTANDARD_PAL_M] = V4L2_STD_PAL_M, +}; + +void pvr2_eeprom_set_default_standard(struct pvr2_hdw *hdw) +{ + int vstd_value = 0; + int vstd_found = 0; + unsigned int idx; + v4l2_std_id vs = (v4l2_std_id)hdw->video_standards; + + for (idx = 0; idx < sizeof(std_choices)/sizeof(std_choices[0]); + idx++) { + if (!(vs & std_choices[idx])) continue; + trace_eeprom("Detected video standard %s (from eeprom)", + pvr2_hdw_get_ctl_value_name( + hdw,PVR2_CID_VIDEOSTANDARD,idx)); + if (vstd_found) continue; + vstd_value = idx; + vstd_found = !0; + } + + if (!vstd_found) { + trace_eeprom("eeprom unable to recognize" + " a known video standard"); + return; + } + + trace_eeprom("Setting initial video standard to %s" + " (detected from eeprom)", + pvr2_hdw_get_ctl_value_name(hdw, + PVR2_CID_VIDEOSTANDARD, + vstd_value)); + pvr2_hdw_set_ctl_value_internal(hdw,PVR2_CID_VIDEOSTANDARD,vstd_value); +} + + +/* + 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/linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.h b/linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.h new file mode 100644 index 000000000..061cecd91 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-eeprom.h @@ -0,0 +1,41 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 *); +void pvr2_eeprom_set_default_standard(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/linux/drivers/media/video/pvrusb2/pvrusb2-encoder.c b/linux/drivers/media/video/pvrusb2/pvrusb2-encoder.c new file mode 100644 index 000000000..6d797cb55 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-encoder.c @@ -0,0 +1,494 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 // for linux/firmware.h +#include +#include "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; + int vd_std = hdw->controls[PVR2_CID_VIDEOSTANDARD].value; + int height = hdw->controls[PVR2_CID_VRES].value; + int width = hdw->controls[PVR2_CID_HRES].value; + int height_full = !hdw->controls[PVR2_CID_INTERLACE].value; + + int is_30fps, is_ntsc; + + switch (vd_std) { + case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: + is_ntsc=1; + is_30fps=1; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_M: + is_ntsc=0; + is_30fps=1; + break; + default: + is_ntsc=0; + is_30fps=0; + break; + } + + pvr2_trace(PVR2_TRACE_ENCODER,"pvr2_encoder_configure"); + + /* set stream output port. */ + ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_OUTPUT_PORT, 2, + 0x01, 0x01); + + /* 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 some 0xdc */ + + /* Mike Isely 19-Jun-2005 I've confirmed + that the Windows driver seems to issue these commands, but + right now I have no idea what these do (and neither does + the ivtv driver). But, if I leave them in, then mplayer + goes nuts with xrun errors. So for now we don't do this. + It sure would be nice to know what these are for. */ +#ifdef notdef + ret |= pvr2_write_encoder_vcmd(hdw, 0xdc, 1, 5); + ret |= pvr2_write_encoder_vcmd(hdw, 0xdc, 2, 3, 1); + ret |= pvr2_write_encoder_vcmd(hdw, 0xdc, 1, 8); +#endif + + /* Strange compared to ivtv data. */ +#ifdef notdef + ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, + 0x0120, 0x0120); + ret |= pvr2_write_encoder_vcmd(hdw, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, + 0x0131, 0x0131); +#endif + 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->controls[PVR2_CID_VBR].value ? 1 : 0), + hdw->controls[PVR2_CID_AVERAGEVIDEOBITRATE].value, + (u32) (hdw->controls[PVR2_CID_PEAKVIDEOBITRATE].value) / 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->controls[ + PVR2_CID_AUDIOBITRATE].value] | + pvr_tbl_srate[hdw->controls[PVR2_CID_SRATE].value] | + hdw->controls[PVR2_CID_AUDIOLAYER].value << 2 | + (hdw->controls[PVR2_CID_AUDIOCRC].value ? 1 << 14 : 0) | + pvr_tbl_emphasis[hdw->controls[ + PVR2_CID_AUDIOEMPHASIS].value]); + + 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 |= PVR2_SUBSYS_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 |= PVR2_SUBSYS_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 &= ~PVR2_SUBSYS_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/linux/drivers/media/video/pvrusb2/pvrusb2-encoder.h b/linux/drivers/media/video/pvrusb2/pvrusb2-encoder.h new file mode 100644 index 000000000..01b5a0b89 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-encoder.h @@ -0,0 +1,42 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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/linux/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h b/linux/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h new file mode 100644 index 000000000..b18906ea6 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h @@ -0,0 +1,224 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 "compat.h" +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) +#include +#include +#else +#include +#endif +#include "pvrusb2-hdw.h" +#include "pvrusb2-io.h" + +#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; + +struct pvr2_ctl_state { + int value; + int dirty; +}; + +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 *); +}; + +#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 + +/* 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; + + /* Video spigot */ + struct pvr2_stream *vid_stream; + + /* Mutex for all hardware state control */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) + struct mutex big_lock_mutex; +#else + struct semaphore big_lock_mutex; +#endif + 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; + 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; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) + struct mutex i2c_list_lock; +#else + struct semaphore i2c_list_lock; +#endif + + /* Frequency table */ + unsigned int freqTable[FREQTABLE_SIZE]; + + /* Stuff for handling low level control interaction with device */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) + struct mutex ctl_lock_mutex; +#else + struct semaphore ctl_lock_mutex; +#endif + 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 long video_standards; + + 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; + + /* Every last bit of controllable state */ + struct pvr2_ctl_state controls[PVR2_CID_COUNT]; +}; + +int pvr2_hdw_set_ctl_value_internal(struct pvr2_hdw *hdw, + unsigned int ctl_id,int value); +int pvr2_hdw_commit_ctl_internal(struct pvr2_hdw *hdw); + + +#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/linux/drivers/media/video/pvrusb2/pvrusb2-hdw.c b/linux/drivers/media/video/pvrusb2/pvrusb2-hdw.c new file mode 100644 index 000000000..1ee2fcda4 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-hdw.c @@ -0,0 +1,2430 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 "compat.h" +#include +#include +#include +#include +#include +#include "pvrusb2.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" + +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 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(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 + +typedef int (*pvr2_ctl_set_func)(struct pvr2_hdw *,int ctl_id,int val); +typedef int (*pvr2_ctl_get_func)(struct pvr2_hdw *,int ctl_id); + +struct pvr2_ctl_def { + const char *name; + pvr2_ctl_set_func set_func; + pvr2_ctl_get_func get_func; + int max_value; + int min_value; + int skip_init; + int default_value; + const char **value_defs_ptr; + unsigned int value_defs_count; +}; + + +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_videostandard[] = { + [PVR2_CVAL_VIDEOSTANDARD_NTSC_M] = "NTSC-M", + [PVR2_CVAL_VIDEOSTANDARD_SECAM_L] = "SECAM-L", + [PVR2_CVAL_VIDEOSTANDARD_PAL_BG] = "PAL-BG", + [PVR2_CVAL_VIDEOSTANDARD_PAL_I] = "PAL-I", + [PVR2_CVAL_VIDEOSTANDARD_PAL_DK] = "PAL-DK", + [PVR2_CVAL_VIDEOSTANDARD_PAL_M] = "PAL-M", +}; + + +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[] = { + [PVR2_CVAL_AUDIOMODE_MONO] = "Mono", + [PVR2_CVAL_AUDIOMODE_STEREO] = "Stereo", + [PVR2_CVAL_AUDIOMODE_SAP] = "SAP", + [PVR2_CVAL_AUDIOMODE_LANG1] = "Lang1", + [PVR2_CVAL_AUDIOMODE_LANG2] = "Lang2", +}; + + +static const char *control_values_hsm[] = { + [PVR2_CVAL_HSM_FAIL] = "Fail", + [PVR2_CVAL_HSM_HIGH] = "High", + [PVR2_CVAL_HSM_FULL] = "Full", +}; + + +#define VDEF(x) \ + .value_defs_ptr = x, \ + .value_defs_count = (sizeof(x)/sizeof(x[0])) + +static int pvr2_ctl_set_chanprog_id(struct pvr2_hdw *hdw,int ctl_id,int value); +static int pvr2_ctl_get_chanprog_id(struct pvr2_hdw *hdw,int ctl_id); +static int pvr2_ctl_get_signal(struct pvr2_hdw *hdw,int ctl_id); +static int pvr2_ctl_get_streaming(struct pvr2_hdw *hdw,int ctl_id); +static int pvr2_ctl_get_hsm(struct pvr2_hdw *hdw,int ctl_id); +static int pvr2_ctl_get_subsys_mask(struct pvr2_hdw *hdw,int ctl_id); +static int pvr2_ctl_set_subsys_mask(struct pvr2_hdw *hdw,int ctl_id,int val); +static int pvr2_ctl_get_subsys_stream_mask(struct pvr2_hdw *hdw,int ctl_id); +static int pvr2_ctl_set_subsys_stream_mask(struct pvr2_hdw *hdw,int ctl_id, + int val); + +static struct pvr2_ctl_def control_defs[PVR2_CID_COUNT] = +{ + [PVR2_CID_BRIGHTNESS] = { + .name = "Brightness", + .min_value = 0, + .max_value = 255, + .default_value = 128, + }, + [PVR2_CID_CONTRAST] = { + .name = "Contrast", + .min_value = 0, + .max_value = 127, + .default_value = 68, + }, + [PVR2_CID_SATURATION] = { + .name = "Saturation", + .min_value = 0, + .max_value = 127, + .default_value = 64, + }, + [PVR2_CID_HUE] = { + .name = "Hue", + .min_value = -128, + .max_value = 127, + .default_value = 0, + }, + [PVR2_CID_VOLUME] = { + .name = "Volume", + .min_value = 0, + .max_value = 65535, + .default_value = 65535, + }, + [PVR2_CID_BALANCE] = { + .name = "Balance", + .min_value = -32768, + .max_value = 32767, + .default_value = 0, + }, + [PVR2_CID_BASS] = { + .name = "Bass", + .min_value = -32768, + .max_value = 32767, + .default_value = 0, + }, + [PVR2_CID_TREBLE] = { + .name = "Treble", + .min_value = -32768, + .max_value = 32767, + .default_value = 0, + }, + [PVR2_CID_MUTE] = { + .name = "Mute", + .min_value = 0, + .max_value = 1, + .default_value = 0, + }, + [PVR2_CID_SRATE] = { + .name = "Sample rate", + .min_value = PVR2_CVAL_SRATE_MIN, + .max_value = PVR2_CVAL_SRATE_MAX, + .default_value = PVR2_CVAL_SRATE_48, + VDEF(control_values_srate), + }, + [PVR2_CID_AUDIOBITRATE] = { + .name = "Audio Bitrate", + .min_value = PVR2_CVAL_AUDIOBITRATE_MIN, + .max_value = PVR2_CVAL_AUDIOBITRATE_MAX, + .default_value = PVR2_CVAL_AUDIOBITRATE_224, + VDEF(control_values_audiobitrate), + }, + [PVR2_CID_AUDIOCRC] = { + .name = "Audio CRC", + .min_value = 0, + .max_value = 1, + .default_value = 1, + }, + [PVR2_CID_AUDIOEMPHASIS] = { + .name = "Audio Emphasis", + .min_value = PVR2_CVAL_AUDIOEMPHASIS_MIN, + .max_value = PVR2_CVAL_AUDIOEMPHASIS_MAX, + .default_value = PVR2_CVAL_AUDIOEMPHASIS_NONE, + VDEF(control_values_audioemphasis), + }, + [PVR2_CID_VBR] = { + .name = "Variable video bitrate", + .min_value = 0, + .max_value = 1, + .default_value = 0, + }, + [PVR2_CID_AVERAGEVIDEOBITRATE] = { + .name = "Average video bitrate", + .min_value = 1, + .max_value = 20000000, + .default_value = 6000000, + }, + [PVR2_CID_PEAKVIDEOBITRATE] = { + .name = "Peak video bitrate", + .min_value = 1, + .max_value = 20000000, + .default_value = 6000000, + }, + [PVR2_CID_VIDEOSTANDARD] = { + .name = "Video Standard", + .min_value = PVR2_CVAL_VIDEOSTANDARD_MIN, + .max_value = PVR2_CVAL_VIDEOSTANDARD_MAX, + .default_value = PVR2_CVAL_VIDEOSTANDARD_NTSC_M, + VDEF(control_values_videostandard), + }, + [PVR2_CID_INPUT] = { + .name = "Video Source", + .min_value = PVR2_CVAL_INPUT_MIN, + .max_value = PVR2_CVAL_INPUT_MAX, + .default_value = PVR2_CVAL_INPUT_TV, + VDEF(control_values_input), + }, + [PVR2_CID_AUDIOMODE] = { + .name = "Audio Mode", + .min_value = PVR2_CVAL_AUDIOMODE_MIN, + .max_value = PVR2_CVAL_AUDIOMODE_MAX, + .default_value = PVR2_CVAL_AUDIOMODE_STEREO, + VDEF(control_values_audiomode), + }, + [PVR2_CID_FREQUENCY] = { + .name = "Tuner Frequency (Hz)", + .min_value = 55250000L, + .max_value = 801250000L, + .default_value = 175250000L, + }, + [PVR2_CID_HRES] = { + .name = "Horizontal capture resolution", + .min_value = 320, + .max_value = 720, + .default_value = 720, + }, + [PVR2_CID_VRES] = { + .name = "Vertical capture resolution", + .min_value = 200, + .max_value = 625, + .default_value = 480, + }, + [PVR2_CID_INTERLACE] = { + .name = "Interlace mode", + .min_value = 0, + .max_value = 1, + .default_value = 0, + }, + [PVR2_CID_AUDIOLAYER] = { + .name = "Audio Layer", + .min_value = 0, /* This is all a wild guess */ + .max_value = 3, + .default_value = 2, /* Appears to be all that is supported */ + }, + [PVR2_CID_CHANNEL] = { + .name = "Channel", + .min_value = 0, + .max_value = FREQTABLE_SIZE, + .default_value = 0, + }, + [PVR2_CID_CHANPROG_ID] = { + .name = "Channel Program ID", + .min_value = 0, + .max_value = FREQTABLE_SIZE, + .default_value = 0, + }, + [PVR2_CID_CHANPROG_FREQ] = { + .name = "Channel Program Frequency", + .min_value = 55250000L, + .max_value = 801250000L, + .skip_init = !0, + .set_func = pvr2_ctl_set_chanprog_id, + .get_func = pvr2_ctl_get_chanprog_id, + }, + [PVR2_CID_SIGNAL_PRESENT] = { + .name = "Signal Present", + .min_value = 0, + .max_value = 1, + .get_func = pvr2_ctl_get_signal, + }, + [PVR2_CID_STREAMING_ENABLED] = { + .name = "Streaming Enabled", + .min_value = 0, + .max_value = 1, + .get_func = pvr2_ctl_get_streaming, + }, + [PVR2_CID_HSM] = { + .name = "USB Speed", + .min_value = PVR2_CVAL_HSM_MIN, + .max_value = PVR2_CVAL_HSM_MAX, + .get_func = pvr2_ctl_get_hsm, + VDEF(control_values_hsm), + }, + [PVR2_CID_SUBSYS_MASK] = { + .name = "Subsystem enabled mask", + .min_value = 0, + .max_value = 0x7fffffff, + .skip_init = !0, + .get_func = pvr2_ctl_get_subsys_mask, + .set_func = pvr2_ctl_set_subsys_mask, + }, + [PVR2_CID_SUBSYS_STREAM_MASK] = { + .name = "Subsystem stream mask", + .min_value = 0, + .max_value = 0x7fffffff, + .skip_init = !0, + .get_func = pvr2_ctl_get_subsys_stream_mask, + .set_func = pvr2_ctl_set_subsys_stream_mask, + }, +}; + +#undef VDEF + + +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 ""; +} + + +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[] = { + "v4l-fx2-pvrusb2.fw", + }; + + hdw->fw1_state = FW1_STATE_FAILED; // default result + + trace_firmware("pvr2_upload_firmware1"); + + ret = pvr2_locate_firmware(hdw,&fw_entry,"fx2 controller", + sizeof(fw_files)/sizeof(fw_files[0]), + fw_files); + 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[] = { + "v4l-cx2341x-enc.fw", + }; + + 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_hdw_cmd_soft_reset(hdw); + 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 |= PVR2_SUBSYS_ENC_FIRMWARE; + } + return ret; +} + + +#define FIRMWARE_RECOVERY_BITS \ + (PVR2_SUBSYS_ENC_CFG | \ + PVR2_SUBSYS_ENC_RUN | \ + PVR2_SUBSYS_ENC_FIRMWARE | \ + PVR2_SUBSYS_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 & PVR2_SUBSYS_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 & PVR2_SUBSYS_USBSTREAM_RUN) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_hdw_cmd_usbstream(0)"); + pvr2_hdw_cmd_usbstream(hdw,0); + } + if (vmsk & PVR2_SUBSYS_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 &= + ~PVR2_SUBSYS_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 & PVR2_SUBSYS_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 & PVR2_SUBSYS_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 & PVR2_SUBSYS_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 |= + PVR2_SUBSYS_DIGITIZER_RUN; + } + if (vmsk & PVR2_SUBSYS_USBSTREAM_RUN) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_hdw_cmd_usbstream(1)"); + pvr2_hdw_cmd_usbstream(hdw,!0); + } + if (vmsk & PVR2_SUBSYS_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); +} + + +static int pvr2_ctl_get_streaming(struct pvr2_hdw *hdw,int ctl_id) +{ + return hdw->flag_streaming_enabled != 0; +} + + +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 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 void pvr2_hdw_setup_low(struct pvr2_hdw *hdw) +{ + int ret; + unsigned int idx; + if (hdw->usb_intf->cur_altsetting->desc.bNumEndpoints == 0) { + 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; + + pvr2_i2c_core_init(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; + } + + for (idx = 0; idx < PVR2_CID_COUNT; idx++) { + if (control_defs[idx].skip_init) continue; + pvr2_hdw_set_ctl_value_internal( + hdw,idx,control_defs[idx].default_value); + } + + pvr2_reset_ctl_endpoints(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + + 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; + } + + 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_eeprom_set_default_standard(hdw); + 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) +{ + unsigned int idx,cnt1,cnt2; + struct pvr2_hdw *hdw; + __u8 ifnum; + + hdw = kmalloc(sizeof(*hdw),GFP_KERNEL); + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_create: hdw=%p",hdw); + if (!hdw) goto fail; + memset(hdw,0,sizeof(*hdw)); + 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 |= PVR2_SUBSYS_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); + 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); +} + + +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); +} + + +static int pvr2_ctl_set_chanprog_id(struct pvr2_hdw *hdw,int ctl_id,int value) +{ + /* This is a special case; the value to store is to an array, and + the element to select is determined by PVR_CID_CHANPROG_ID. */ + int id = hdw->controls[PVR2_CID_CHANPROG_ID].value; + if ((id < 1) || (id > FREQTABLE_SIZE)) return 0; + hdw->freqTable[id-1] = value; + if (hdw->controls[PVR2_CID_CHANNEL].value == id) { + /* If the current channel happens to be the slot we just + set, then act like the current channel just got changed + so we'll update that too. */ + hdw->controls[PVR2_CID_CHANNEL].dirty = !0; + } + return 0; +} + + +static int pvr2_ctl_get_chanprog_id(struct pvr2_hdw *hdw,int ctl_id) +{ + /* This is a special case; the value to return is from an array, + and the element to select is determined by + PVR_CID_CHANPROG_ID. */ + int id = hdw->controls[PVR2_CID_CHANPROG_ID].value; + if ((id < 1) || (id > FREQTABLE_SIZE)) return 0; + return hdw->freqTable[id-1]; +} + + +/* Retrieve current value for a given control */ +int pvr2_hdw_get_ctl_value(struct pvr2_hdw *hdw,unsigned int ctl_id) +{ + int ret = 0; + + if (ctl_id >= PVR2_CID_COUNT) return 0; + LOCK_TAKE(hdw->big_lock); do { + if (control_defs[ctl_id].get_func) { + ret = control_defs[ctl_id].get_func(hdw,ctl_id); + break; + } + ret = hdw->controls[ctl_id].value; + } while(0); LOCK_GIVE(hdw->big_lock); + return ret; +} + + +/* Return true if control is writable */ +int pvr2_hdw_get_ctl_rw(struct pvr2_hdw *hdw,unsigned int ctl_id) +{ + if (ctl_id >= PVR2_CID_COUNT) return 0; + if (control_defs[ctl_id].get_func && !control_defs[ctl_id].set_func) { + return 0; + } + return !0; +} + + +/* Retrieve legal minimum value for a given control */ +int pvr2_hdw_get_ctl_min_value(struct pvr2_hdw *hdw,unsigned int ctl_id) +{ + if (ctl_id >= PVR2_CID_COUNT) return 0; + return control_defs[ctl_id].min_value; +} + + +/* Retrieve legal maximum value for a given control */ +int pvr2_hdw_get_ctl_max_value(struct pvr2_hdw *hdw,unsigned int ctl_id) +{ + if (ctl_id >= PVR2_CID_COUNT) return 0; + return control_defs[ctl_id].max_value; +} + + +/* Set current value for given control - normally this is just stored and + the hardware isn't updated until the commit function is called. */ +int pvr2_hdw_set_ctl_value_internal(struct pvr2_hdw *hdw, + unsigned int ctl_id,int value) +{ + if (ctl_id >= PVR2_CID_COUNT) return -EINVAL; + if (value < control_defs[ctl_id].min_value) return -EINVAL; + if (value > control_defs[ctl_id].max_value) return -EINVAL; + if (control_defs[ctl_id].set_func) { + return control_defs[ctl_id].set_func(hdw,ctl_id,value); + } else if (control_defs[ctl_id].get_func) { + /* If there's no "set" function yet there is still a "get" + function, then treat this as a read-only value. */ + return -EINVAL; + } + if ((hdw->controls[ctl_id].value != value) || (ctlchg != 0)) { + hdw->controls[ctl_id].value = value; + hdw->controls[ctl_id].dirty = !0; + } + return 0; +} + + +/* Set current value for given control - this is just stored; the hardware + isn't updated until the commit function is called. */ +int pvr2_hdw_set_ctl_value(struct pvr2_hdw *hdw,unsigned int ctl_id,int value) +{ + int ret; + LOCK_TAKE(hdw->big_lock); do { + ret = pvr2_hdw_set_ctl_value_internal(hdw,ctl_id,value); + } while (0); LOCK_GIVE(hdw->big_lock); + return ret; +} + + +/* Retrieve string name for a given control value (returns a null pointer + for any invalid combinations). */ +const char *pvr2_hdw_get_ctl_value_name(struct pvr2_hdw *hdw, + unsigned int ctl_id, + int value) +{ + struct pvr2_ctl_def *cdef; + if (ctl_id >= PVR2_CID_COUNT) return 0; + cdef = control_defs + ctl_id; + if (! cdef->value_defs_ptr) return 0; + if (value >= cdef->value_defs_count) return 0; + return cdef->value_defs_ptr[value]; +} + + +/* Retrieve string name for given control */ +const char *pvr2_hdw_get_ctl_name(struct pvr2_hdw *hdw,unsigned int ctl_id) +{ + if (ctl_id >= PVR2_CID_COUNT) return 0; + return control_defs[ctl_id].name; +} + + +/* 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; + int value; + const char *ctl_name; + const char *ctl_value; + int commit_flag = 0; + + /* Let's see if the channel changed and we have to update the + frequency because of it. This setup means one can tune the + receiver either by just setting the channel (using the frequency + table), or by directly programming the frequency. How do we + resolve the obvious conflict here? The direct frequency takes + priority; if directly set then we commit that value and force + the channel to zero which is interpreted to mean "none". If on + the other hand we see that the channel has been set and it's a + legal value, then we copy that into the frequency. The metaphor + here is similar to when you tune your digital radio: You an + either set a frequency directly or punch up a pre-programmed + station. Either way a frequency is set, and if you do use a + preset, then the radio also shows you which preset it is - until + you override that by directly entering a new frequency. */ + if (hdw->controls[PVR2_CID_FREQUENCY].dirty) { + /* Frequency has been directly set, so clear out the + channel. */ + hdw->controls[PVR2_CID_CHANNEL].value = 0; + } else if (hdw->controls[PVR2_CID_CHANNEL].dirty) { + int id = hdw->controls[PVR2_CID_CHANNEL].value; + if ((id > 0) && (id <= FREQTABLE_SIZE)) { + if (hdw->controls[PVR2_CID_FREQUENCY].value != + hdw->freqTable[id-1]) { + hdw->controls[PVR2_CID_FREQUENCY].value = + hdw->freqTable[id-1]; + hdw->controls[PVR2_CID_FREQUENCY].dirty = !0; + } + } + } + + for (idx = 0; idx < PVR2_CID_COUNT; idx++) { + if (!hdw->controls[idx].dirty) continue; + if (!commit_flag) { + commit_flag = !0; + } + value = hdw->controls[idx].value; + ctl_name = control_defs[idx].name; + if (control_defs[idx].value_defs_ptr) { + if (value < control_defs[idx].value_defs_count) { + ctl_value = + control_defs[idx].value_defs_ptr[value]; + } else { + ctl_value = ""; + } + } else { + ctl_value = ""; + } + pvr2_trace(PVR2_TRACE_CTL, + "/*--TRACE_COMMIT--*/ \"%s\" <-- %d (%s)", + ctl_name,value,ctl_value); + } + + 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->controls[PVR2_CID_VIDEOSTANDARD].dirty) { + /* Rewrite the vertical resolution to be appropriate to the + video standard that has been selected. */ + int nvres = hdw->controls[PVR2_CID_VRES].value; + switch (hdw->controls[PVR2_CID_VIDEOSTANDARD].value) { + case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: + case PVR2_CVAL_VIDEOSTANDARD_PAL_M: + nvres = 480; + break; + default: + nvres = 576; + } + if (nvres != hdw->controls[PVR2_CID_VRES].value) { + hdw->controls[PVR2_CID_VRES].value = nvres; + hdw->controls[PVR2_CID_VRES].dirty = !0; + } + if (!hdw->controls[PVR2_CID_INTERLACE].value) { + hdw->controls[PVR2_CID_INTERLACE].value = 0; + hdw->controls[PVR2_CID_INTERLACE].dirty = !0; + } + } + + if (hdw->controls[PVR2_CID_VIDEOSTANDARD].dirty || + hdw->controls[PVR2_CID_VRES].dirty || + hdw->controls[PVR2_CID_HRES].dirty || + hdw->controls[PVR2_CID_INTERLACE].dirty || + hdw->controls[PVR2_CID_VBR].dirty || + hdw->controls[PVR2_CID_AVERAGEVIDEOBITRATE].dirty || + hdw->controls[PVR2_CID_PEAKVIDEOBITRATE].dirty || + hdw->controls[PVR2_CID_AUDIOBITRATE].dirty || + hdw->controls[PVR2_CID_SRATE].dirty || + hdw->controls[PVR2_CID_AUDIOLAYER].dirty || + hdw->controls[PVR2_CID_AUDIOCRC].dirty || + hdw->controls[PVR2_CID_AUDIOEMPHASIS].dirty) { + /* If any of this changes, then the encoder needs to be + reconfigured, and we need to reset the stream. */ + stale_subsys_mask |= PVR2_SUBSYS_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 < PVR2_CID_COUNT; idx++) { + hdw->controls[idx].dirty = 0; + } + + /* 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); +} + + +/* Find out how many controls there are. Legal ids are numbered from 1 + through this value. */ +unsigned int pvr2_hdw_get_ctl_count(struct pvr2_hdw *hdw) +{ + return PVR2_CID_COUNT; +} + + +/* 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->controls[PVR2_CID_INPUT].value) { + 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; +} + + +static int pvr2_ctl_get_subsys_mask(struct pvr2_hdw *hdw,int ctl_id) +{ + return hdw->subsys_enabled_mask; +} + + +static int pvr2_ctl_set_subsys_mask(struct pvr2_hdw *hdw,int ctl_id,int val) +{ + pvr2_hdw_subsys_bit_chg_no_lock(hdw,~0,val); + return 0; +} + + +static int pvr2_ctl_get_subsys_stream_mask(struct pvr2_hdw *hdw,int ctl_id) +{ + return hdw->subsys_stream_mask; +} + + +static int pvr2_ctl_set_subsys_stream_mask(struct pvr2_hdw *hdw,int ctl_id, + int val) +{ + pvr2_hdw_subsys_stream_bit_chg_no_lock(hdw,~0,val); + return 0; +} + + +static int pvr2_ctl_get_hsm(struct pvr2_hdw *hdw,int ctl_id) +{ + int result = pvr2_hdw_is_hsm(hdw); + if (result < 0) return PVR2_CVAL_HSM_FAIL; + if (result) return PVR2_CVAL_HSM_HIGH; + return PVR2_CVAL_HSM_FULL; +} + + +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; +} + + +static int pvr2_ctl_get_signal(struct pvr2_hdw *hdw,int ctl_id) +{ + return ((pvr2_hdw_get_signal_status_internal(hdw) & PVR2_SIGNAL_OK) ? + 1 : 0); +} + + +/* 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(struct pvr2_hdw *hdw, + void *write_data,unsigned int write_len, + void *read_data,unsigned int read_len) +{ + unsigned int idx; + int status; + struct timer_list timer; + if (!hdw->ctl_lock_held) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Attempted to execute control transfer" + " without lock!!"); + status = -EINVAL; + goto done; + } + if (!hdw->flag_ok) { + 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)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Attempted to execute control transfer" + " when USB is disconnected"); + return -EIO; + } + + /* 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 + HZ*4; + 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); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + hdw->ctl_write_urb->transfer_flags |= URB_ASYNC_UNLINK; +#endif + 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; + } + } + + 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); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + hdw->ctl_read_urb->transfer_flags |= URB_ASYNC_UNLINK; +#endif + 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; + } + } + + /* 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; + 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; + 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; + 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; + 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; + 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) { + pvr2_hdw_render_useless_unlocked(hdw); + } + return status; +} + + +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_soft_reset(struct pvr2_hdw *hdw) +{ + int status; + LOCK_TAKE(hdw->ctl_lock); do { + pvr2_trace(PVR2_TRACE_INIT,"Requesting uproc soft reset"); + 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_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 & + ~PVR2_SUBSYS_USBSTREAM_RUN) | + (runFl ? PVR2_SUBSYS_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/linux/drivers/media/video/pvrusb2/pvrusb2-hdw.h b/linux/drivers/media/video/pvrusb2/pvrusb2-hdw.h new file mode 100644 index 000000000..2f9c4cb6d --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-hdw.h @@ -0,0 +1,412 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 +#include "pvrusb2-io.h" + +/* Definition of state variables that we can inspect & change. Numbers are + assigned from zero counting up with no gaps. */ +#define PVR2_CID_BRIGHTNESS 0 +#define PVR2_CID_CONTRAST 1 +#define PVR2_CID_SATURATION 2 +#define PVR2_CID_HUE 3 +#define PVR2_CID_VOLUME 4 +#define PVR2_CID_BALANCE 5 +#define PVR2_CID_BASS 6 +#define PVR2_CID_TREBLE 7 +#define PVR2_CID_MUTE 8 +#define PVR2_CID_SRATE 9 +#define PVR2_CID_AUDIOBITRATE 10 +#define PVR2_CID_AUDIOCRC 11 +#define PVR2_CID_AUDIOEMPHASIS 12 +#define PVR2_CID_VBR 13 +#define PVR2_CID_AVERAGEVIDEOBITRATE 14 +#define PVR2_CID_PEAKVIDEOBITRATE 15 +#define PVR2_CID_VIDEOSTANDARD 16 +#define PVR2_CID_INPUT 17 +#define PVR2_CID_AUDIOMODE 18 +#define PVR2_CID_FREQUENCY 19 // Units of Hz +#define PVR2_CID_HRES 20 +#define PVR2_CID_VRES 21 +#define PVR2_CID_INTERLACE 22 +#define PVR2_CID_AUDIOLAYER 23 +#define PVR2_CID_CHANNEL 24 +#define PVR2_CID_CHANPROG_ID 25 +#define PVR2_CID_CHANPROG_FREQ 26 +#define PVR2_CID_SIGNAL_PRESENT 27 +#define PVR2_CID_STREAMING_ENABLED 28 +#define PVR2_CID_HSM 29 +#define PVR2_CID_SUBSYS_MASK 30 +#define PVR2_CID_SUBSYS_STREAM_MASK 31 + +/* Number of state variables */ +#define PVR2_CID_COUNT 32 + +/* Legal values for the SRATE state variable */ +#define PVR2_CVAL_SRATE_48 0 +#define PVR2_CVAL_SRATE_44_1 1 +#define PVR2_CVAL_SRATE_MIN PVR2_CVAL_SRATE_48 +#define PVR2_CVAL_SRATE_MAX PVR2_CVAL_SRATE_44_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 +#define PVR2_CVAL_AUDIOBITRATE_MIN PVR2_CVAL_AUDIOBITRATE_384 +#define PVR2_CVAL_AUDIOBITRATE_MAX PVR2_CVAL_AUDIOBITRATE_VBR + +/* 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 +#define PVR2_CVAL_AUDIOEMPHASIS_MIN PVR2_CVAL_AUDIOEMPHASIS_NONE +#define PVR2_CVAL_AUDIOEMPHASIS_MAX PVR2_CVAL_AUDIOEMPHASIS_CCITT + +/* Legal values for the VIDEOSTANDARD state variable */ +#define PVR2_CVAL_VIDEOSTANDARD_NTSC_M 0 +#define PVR2_CVAL_VIDEOSTANDARD_PAL_BG 1 +#define PVR2_CVAL_VIDEOSTANDARD_PAL_I 2 +#define PVR2_CVAL_VIDEOSTANDARD_PAL_DK 3 +#define PVR2_CVAL_VIDEOSTANDARD_PAL_M 4 +#define PVR2_CVAL_VIDEOSTANDARD_SECAM_L 5 + +#define PVR2_CVAL_VIDEOSTANDARD_MIN PVR2_CVAL_VIDEOSTANDARD_NTSC_M +#define PVR2_CVAL_VIDEOSTANDARD_MAX PVR2_CVAL_VIDEOSTANDARD_SECAM_L + +/* 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 +#define PVR2_CVAL_INPUT_MIN PVR2_CVAL_INPUT_TV +#define PVR2_CVAL_INPUT_MAX PVR2_CVAL_INPUT_RADIO + +/* Legal values for the AUDIOMODE state variable */ +#define PVR2_CVAL_AUDIOMODE_MONO 0 +#define PVR2_CVAL_AUDIOMODE_STEREO 1 +#define PVR2_CVAL_AUDIOMODE_SAP 2 +#define PVR2_CVAL_AUDIOMODE_LANG1 3 +#define PVR2_CVAL_AUDIOMODE_LANG2 4 +#define PVR2_CVAL_AUDIOMODE_MIN PVR2_CVAL_AUDIOMODE_MONO +#define PVR2_CVAL_AUDIOMODE_MAX PVR2_CVAL_AUDIOMODE_LANG2 + +/* Values that pvr2_hdw_get_signal_status() returns */ +#define PVR2_SIGNAL_OK 0x0001 +#define PVR2_SIGNAL_STEREO 0x0002 +#define PVR2_SIGNAL_SAP 0x0004 + +/* 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_CVAL_HSM_MIN PVR2_CVAL_HSM_FAIL +#define PVR2_CVAL_HSM_MAX PVR2_CVAL_HSM_HIGH + +/* 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_ENC_FIRMWARE (1 << 0) +#define PVR2_SUBSYS_ENC_CFG (1 << 1) +#define PVR2_SUBSYS_DIGITIZER_RUN (1 << 2) +#define PVR2_SUBSYS_USBSTREAM_RUN (1 << 3) +#define PVR2_SUBSYS_ENC_RUN (1 << 4) + +#define PVR2_SUBSYS_CFG_ALL ( \ + PVR2_SUBSYS_ENC_FIRMWARE | \ + PVR2_SUBSYS_ENC_CFG ) +#define PVR2_SUBSYS_RUN_ALL ( \ + PVR2_SUBSYS_DIGITIZER_RUN | \ + PVR2_SUBSYS_USBSTREAM_RUN | \ + PVR2_SUBSYS_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); + +/* 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 *); + +/* Retrieve current value for a given control */ +int pvr2_hdw_get_ctl_value(struct pvr2_hdw *,unsigned int ctl_id); + +/* Return true if control is writable */ +int pvr2_hdw_get_ctl_rw(struct pvr2_hdw *,unsigned int ctl_id); + +/* Retrieve legal minimum value for a given control */ +int pvr2_hdw_get_ctl_min_value(struct pvr2_hdw *,unsigned int ctl_id); + +/* Retrieve legal maximum value for a given control */ +int pvr2_hdw_get_ctl_max_value(struct pvr2_hdw *,unsigned int ctl_id); + +/* Set current value for given control - this is just stored; the hardware + isn't updated until the commit function is called. */ +int pvr2_hdw_set_ctl_value(struct pvr2_hdw *,unsigned int ctl_id,int value); + +/* Retrieve string name for given control */ +const char *pvr2_hdw_get_ctl_name(struct pvr2_hdw *,unsigned int ctl_id); + +/* Retrieve string name for a given control value (returns a null pointer + for any invalid combinations). */ +const char *pvr2_hdw_get_ctl_value_name(struct pvr2_hdw *, + unsigned int ctl_id,int value); + +/* Commit all control changes made up to this point */ +int pvr2_hdw_commit_ctl(struct pvr2_hdw *); + +/* Find out how many controls there are. Legal ids are numbered from 0 + through this value - 1. */ +unsigned int pvr2_hdw_get_ctl_count(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 *); + +/* 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 *, unsigned int, + void *, unsigned int); + +/* 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_soft_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); + + +#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/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c new file mode 100644 index 000000000..2d97653d5 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c @@ -0,0 +1,102 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 "compat.h" +#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" + +#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; + } + } + if (id == I2C_DRIVERID_SAA711X) { + if (pvr2_i2c_decoder_v4l_setup(hdw,cp)) { + return; + } + } +#ifdef PVR2_ENABLE_DRIVERID_TDA9887 + if (id == I2C_DRIVERID_TDA9887) { + if (pvr2_i2c_demod_setup(hdw,cp)) { + return; + } + } +#endif +} + + +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/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c new file mode 100644 index 000000000..69864782b --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c @@ -0,0 +1,250 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 +#include + + +static void set_standard(struct pvr2_hdw *hdw) +{ + int cvstd = hdw->controls[PVR2_CID_VIDEOSTANDARD].value; + v4l2_std_id vs; + pvr2_trace(PVR2_TRACE_CHIPS, + "i2c v4l2 set_standard(%d)",cvstd); + + switch (cvstd) { + default: + case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: + vs = V4L2_STD_NTSC_M; + break; + case PVR2_CVAL_VIDEOSTANDARD_SECAM_L: + vs = V4L2_STD_SECAM; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_BG: + vs = V4L2_STD_PAL_BG; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_I: + vs = V4L2_STD_PAL_I; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_DK: + vs = V4L2_STD_PAL_DK; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_M: + vs = V4L2_STD_PAL_M; + break; + } + pvr2_i2c_core_cmd(hdw,VIDIOC_S_STD,&vs); +} + + +static int check_standard(struct pvr2_hdw *hdw) +{ + return hdw->controls[PVR2_CID_VIDEOSTANDARD].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->controls[PVR2_CID_BRIGHTNESS].value, + hdw->controls[PVR2_CID_CONTRAST].value, + hdw->controls[PVR2_CID_SATURATION].value, + hdw->controls[PVR2_CID_HUE].value); + memset(&ctrl,0,sizeof(ctrl)); + ctrl.id = V4L2_CID_BRIGHTNESS; + ctrl.value = hdw->controls[PVR2_CID_BRIGHTNESS].value; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_CONTRAST; + ctrl.value = hdw->controls[PVR2_CID_CONTRAST].value; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_SATURATION; + ctrl.value = hdw->controls[PVR2_CID_SATURATION].value; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_HUE; + ctrl.value = hdw->controls[PVR2_CID_HUE].value; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); +} + + +static int check_bcsh(struct pvr2_hdw *hdw) +{ + return (hdw->controls[PVR2_CID_BRIGHTNESS].dirty || + hdw->controls[PVR2_CID_CONTRAST].dirty || + hdw->controls[PVR2_CID_SATURATION].dirty || + hdw->controls[PVR2_CID_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->controls[PVR2_CID_VOLUME].value, + hdw->controls[PVR2_CID_BALANCE].value, + hdw->controls[PVR2_CID_BASS].value, + hdw->controls[PVR2_CID_TREBLE].value, + hdw->controls[PVR2_CID_MUTE].value); + memset(&ctrl,0,sizeof(ctrl)); + ctrl.id = V4L2_CID_AUDIO_MUTE; + ctrl.value = hdw->controls[PVR2_CID_MUTE].value ? 1 : 0; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_AUDIO_VOLUME; + ctrl.value = hdw->controls[PVR2_CID_VOLUME].value; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_AUDIO_BALANCE; + ctrl.value = hdw->controls[PVR2_CID_BALANCE].value; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_AUDIO_BASS; + ctrl.value = hdw->controls[PVR2_CID_BASS].value; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_AUDIO_TREBLE; + ctrl.value = hdw->controls[PVR2_CID_TREBLE].value; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); +} + + +static int check_volume(struct pvr2_hdw *hdw) +{ + return (hdw->controls[PVR2_CID_VOLUME].dirty || + hdw->controls[PVR2_CID_BALANCE].dirty || + hdw->controls[PVR2_CID_BASS].dirty || + hdw->controls[PVR2_CID_TREBLE].dirty || + hdw->controls[PVR2_CID_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->controls[PVR2_CID_FREQUENCY].value; + 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->controls[PVR2_CID_FREQUENCY].dirty != 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->controls[PVR2_CID_HRES].value; + fmt.fmt.pix.height = hdw->controls[PVR2_CID_VRES].value; + + 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->controls[PVR2_CID_HRES].dirty || + hdw->controls[PVR2_CID_VRES].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", +}; + + +/* + 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/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h new file mode 100644 index 000000000..fa0a3af7e --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h @@ -0,0 +1,46 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 "compat.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; + +#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/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c new file mode 100644 index 000000000..e682538ef --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c @@ -0,0 +1,765 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 "compat.h" +#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; + +#ifdef notdef + trace_i2c("pvr2_i2c_write"); +#endif + + if (!data) length = 0; + if (length > (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]); + } + } + } +#ifdef notdef + trace_i2c("i2c_write(%d) len=%d ret=%d stat=%d",i2c_addr,length,ret, + hdw->cmd_buffer[0]); +#endif + + 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; + +#ifdef notdef + trace_i2c("pvr2_i2c_read"); +#endif + + if (!data) dlen = 0; + if (dlen > (sizeof(hdw->cmd_buffer) - 4)) return -ENOTSUPP; + if (res && (rlen > (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]); + } + } + } + +#ifdef notdef + trace_i2c("i2c_read(%d) wlen=%d rlen=%d ret=%d stat=%d", + i2c_addr,dlen,rlen,ret,hdw->cmd_buffer[0]); +#endif + /* 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 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; + struct pvr2_hdw *hdw = (struct pvr2_hdw *)(i2c_adap->algo_data); + + if ((msgs[0].flags & I2C_M_NOSTART)) { + trace_i2c("i2c refusing I2C_M_NOSTART"); + 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 (pvr2_i2c_read(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 (pvr2_i2c_read(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 (pvr2_i2c_write(hdw,msgs[0].addr, + msgs[0].buf,msgs[0].len)) { + ret = -EIO; + } + goto done; + } + } else if (num == 2) { + 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 (pvr2_i2c_read(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; + for (idx = 0; idx < num; idx++) { + printk(KERN_INFO + "pvrusb2 i2c xfer %u/%u:" + " addr=0x%x len=%d %s%s", + idx+1,num, + msgs[idx].addr, + msgs[idx].len, + (msgs[idx].flags & I2C_M_RD ? + "read" : "write"), + (msgs[idx].flags & I2C_M_NOSTART ? + " nostart" : "")); + 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; +} + +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 LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16) + if (!try_module_get(cp->driver->owner)) return -EAGAIN; +#else + if (!try_module_get(cp->driver->driver.owner)) return -EAGAIN; +#endif + stat = cp->driver->command(cp,cmd,arg); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16) + module_put(cp->driver->owner); +#else + module_put(cp->driver->driver.owner); +#endif + return stat; +} + +int pvr2_i2c_client_cmd(struct pvr2_i2c_client *cp,unsigned int cmd,void *arg) +{ + if (pvrusb2_debug & PVR2_TRACE_I2C_CMD) { + char buf[100]; + unsigned int cnt; + cnt = pvr2_i2c_client_describe(cp,PVR2_I2C_DETAIL_DEBUG, + buf,sizeof(buf)); + pvr2_trace(PVR2_TRACE_I2C_CMD, + "i2c COMMAND to %.*s",cnt,buf); + } + return pvr2_i2c_core_singleton(cp->client,cmd,arg); +} + +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=]", + client->name, + client->addr); + } + return 0; +} + +static struct i2c_algorithm pvr2_i2c_algo_template = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + .id = I2C_HW_B_BT848, +#endif + .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) +{ + 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)); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + strlcpy(hdw->i2c_algo.name,hdw->name,sizeof(hdw->i2c_algo.name)); +#endif + 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/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h new file mode 100644 index 000000000..e8af5b0ed --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h @@ -0,0 +1,96 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 +#include + +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/linux/drivers/media/video/pvrusb2/pvrusb2-io.c b/linux/drivers/media/video/pvrusb2/pvrusb2-io.c new file mode 100644 index 000000000..b01975af4 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-io.c @@ -0,0 +1,716 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 "compat.h" +#include "pvrusb2-io.h" +#include "pvrusb2-debug.h" +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) +#include +#include +#else +#include +#endif + +#define BUFFER_SIG 0x47653271 + +// #define SANITY_CHECK_BUFFERS + +#ifdef notdef +#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) +#endif + +#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; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) + struct mutex mutex; +#else + struct semaphore mutex; +#endif + /* 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/linux/drivers/media/video/pvrusb2/pvrusb2-io.h b/linux/drivers/media/video/pvrusb2/pvrusb2-io.h new file mode 100644 index 000000000..65e11385b --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-io.h @@ -0,0 +1,102 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 +#include + +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/linux/drivers/media/video/pvrusb2/pvrusb2-ioread.c b/linux/drivers/media/video/pvrusb2/pvrusb2-ioread.c new file mode 100644 index 000000000..4182d75b7 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-ioread.c @@ -0,0 +1,367 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 "compat.h" +#include "pvrusb2-ioread.h" +#include "pvrusb2-debug.h" +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) +#include +#include +#else +#include +#endif +#include + +#define BUFFER_COUNT 32 +#define BUFFER_SIZE PAGE_ALIGN(0x4000) + +struct pvr2_ioread { + struct pvr2_stream *stream; + char *buffer_storage[BUFFER_COUNT]; + int resid_offs; + int enabled; + int stream_running; + int spigot_open; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) + struct mutex mutex; +#else + struct semaphore mutex; +#endif +}; + +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); + kfree(cp); +} + +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->resid_offs = 0; + cp->enabled = 0; + cp->stream_running = 0; + cp->spigot_open = 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); + cp->resid_offs = 0; + 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->stream_running = 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_avail(struct pvr2_ioread *cp) +{ + int ret; + if (!(cp->enabled)) { + // Stream is not enabled; so this is an I/O error + return -EIO; + } + + 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; + struct pvr2_buffer *bp; + 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 (cnt) { + bp = pvr2_stream_get_ready_buffer(cp->stream); + if (!bp) break; // Nothing ready; done + + bcnt = pvr2_buffer_get_count(bp); + if (!bcnt) { + // Nothing transferred. Was there an + // error? + stat = pvr2_buffer_get_status(bp); + 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); + ret = -EIO; + break; + } + src = 0; + } else { + // Calculate buffer offset and count + src = cp->buffer_storage[ + pvr2_buffer_get_id(bp)]; + if (cp->resid_offs > bcnt) { + cp->resid_offs = bcnt; + } + src += cp->resid_offs; + bcnt -= cp->resid_offs; + } + if (bcnt > cnt) { + // Can't read the entire buffer this time + // so remember how far in we're going to + // get so we can resume it later + bcnt = cnt; + cp->resid_offs += bcnt; + } else { + // We will be able to get the whole thing, + // so clear the residual offset + cp->resid_offs = 0; + } + if (bcnt) { + 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->resid_offs) break; // If there's a residual + // offset, get out + // Queue the buffer now that we've consumed it. + stat = pvr2_buffer_queue(bp); + 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); + ret = stat; + break; + } + } + + } 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/linux/drivers/media/video/pvrusb2/pvrusb2-ioread.h b/linux/drivers/media/video/pvrusb2/pvrusb2-ioread.h new file mode 100644 index 000000000..e6205f123 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-ioread.h @@ -0,0 +1,47 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 *); +int pvr2_ioread_set_enabled(struct pvr2_ioread *,int); +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/linux/drivers/media/video/pvrusb2/pvrusb2-main.c b/linux/drivers/media/video/pvrusb2/pvrusb2-main.c new file mode 100644 index 000000000..0003c7a07 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-main.c @@ -0,0 +1,183 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 "compat.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pvrusb2-hdw.h" +#include "pvrusb2-context.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-v4l2.h" +#include "pvrusb2-sysfs.h" + +#define DRIVER_AUTHOR "Mike Isely " +#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"); + +static struct pvr2_sysfs_class *class_ptr = 0; + +static struct usb_device_id pvr_table[] = { + { USB_DEVICE(0x2040, 0x2900) }, + { } +}; + +static void pvr_setup_attach(struct pvr2_context *pvr) +{ + /* Create association with v4l layer */ + pvr2_v4l2_create(pvr); + pvr2_sysfs_create(pvr,class_ptr); +} + +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,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 = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16) + owner: THIS_MODULE, +#endif + name: "pvrusb2", + id_table: pvr_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"); + + /* Auto-load various support modules (with which we may + indirectly interact) */ + request_module("tuner"); + request_module("tveeprom"); + request_module("msp3400"); + request_module("saa7115"); + request_module("tda9887"); + + class_ptr = pvr2_sysfs_class_create(); + + 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"); + + pvr2_sysfs_class_destroy(class_ptr); + + usb_deregister(&pvr_driver); +} + +module_init(pvr_init); +module_exit(pvr_exit); + +MODULE_DEVICE_TABLE (usb, pvr_table); +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/linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.c b/linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.c new file mode 100644 index 000000000..a9710b52c --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.c @@ -0,0 +1,806 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 +#include +#include +#include "compat.h" +#include "pvrusb2-sysfs.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-debugifc.h" + +#define pvr2_sysfs_trace(...) pvr2_trace(PVR2_TRACE_SYSFS,__VA_ARGS__) + +static char *item_names[PVR2_CID_COUNT] = { + [PVR2_CID_BRIGHTNESS] = "ctl_brightness", + [PVR2_CID_CONTRAST] = "ctl_contrast", + [PVR2_CID_SATURATION] = "ctl_saturation", + [PVR2_CID_HUE] = "ctl_hue", + [PVR2_CID_VOLUME] = "ctl_volume", + [PVR2_CID_BALANCE] = "ctl_balance", + [PVR2_CID_BASS] = "ctl_bass", + [PVR2_CID_TREBLE] = "ctl_treble", + [PVR2_CID_MUTE] = "ctl_mute", + [PVR2_CID_SRATE] = "ctl_srate", + [PVR2_CID_AUDIOBITRATE] = "ctl_audio_bitrate", + [PVR2_CID_AUDIOCRC] = "ctl_audio_crc", + [PVR2_CID_AUDIOEMPHASIS] = "ctl_audio_emphasis", + [PVR2_CID_VBR] = "ctl_vbr", + [PVR2_CID_AVERAGEVIDEOBITRATE] = "ctl_video_average_bitrate", + [PVR2_CID_PEAKVIDEOBITRATE] = "ctl_video_peak_bitrate", + [PVR2_CID_VIDEOSTANDARD] = "ctl_video_standard", + [PVR2_CID_INPUT] = "ctl_input", + [PVR2_CID_AUDIOMODE] = "ctl_audio_mode", + [PVR2_CID_FREQUENCY] = "ctl_frequency", + [PVR2_CID_HRES] = "ctl_resolution_hor", + [PVR2_CID_VRES] = "ctl_resolution_ver", + [PVR2_CID_INTERLACE] = "ctl_interlace", + [PVR2_CID_AUDIOLAYER] = "ctl_audio_layer", + [PVR2_CID_CHANNEL] = "ctl_channel", + [PVR2_CID_CHANPROG_ID] = "ctl_freq_table_channel", + [PVR2_CID_CHANPROG_FREQ] = "ctl_freq_table_value", + [PVR2_CID_SIGNAL_PRESENT] = "ctl_signal_present", + [PVR2_CID_STREAMING_ENABLED] = "ctl_streaming_enabled", + [PVR2_CID_HSM] = "ctl_usb_speed", + [PVR2_CID_SUBSYS_MASK] = "ctl_debug_subsys_mask", + [PVR2_CID_SUBSYS_STREAM_MASK] = "ctl_debug_subsys_stream_mask", +}; + +struct pvr2_sysfs { + struct pvr2_channel channel; + struct class_device *class_dev; + struct pvr2_sysfs_debugifc *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; +}; + +struct pvr2_sysfs_debugifc { + struct class_device_attribute attr_debugcmd; + struct class_device_attribute attr_debuginfo; +}; + +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_val; + int attr_id; + struct pvr2_sysfs *chptr; + struct pvr2_sysfs_ctl_item *item_next; + struct attribute *attr_gen[5]; + struct attribute_group grp; +}; + +struct pvr2_sysfs_class { + struct class class; +}; + +static ssize_t show_name(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + const char *name; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + name = pvr2_hdw_get_ctl_name(sfp->channel.hdw,id); + + 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_sysfs *sfp; + int val; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + val = pvr2_hdw_get_ctl_min_value(sfp->channel.hdw,id); + + pvr2_sysfs_trace("pvr2_sysfs(%p) show_min(cid=%d) is %d",sfp,id,val); + + return scnprintf(buf,PAGE_SIZE,"%d\n",val); +} + +static ssize_t show_max(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + int val; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + val = pvr2_hdw_get_ctl_max_value(sfp->channel.hdw,id); + + pvr2_sysfs_trace("pvr2_sysfs(%p) show_max(cid=%d) is %d",sfp,id,val); + + return scnprintf(buf,PAGE_SIZE,"%d\n",val); +} + +static ssize_t show_val_int(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + int val; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + val = pvr2_hdw_get_ctl_value(sfp->channel.hdw,id); + + pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_int(cid=%d) is %d", + sfp,id,val); + + return scnprintf(buf,PAGE_SIZE,"%d\n",val); +} + +static ssize_t show_val_enum(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + int val; + const char *name; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + val = pvr2_hdw_get_ctl_value(sfp->channel.hdw,id); + + name = pvr2_hdw_get_ctl_value_name(sfp->channel.hdw,id,val); + + pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_enum(cid=%d) is %s (%d)", + sfp,id,name,val); + + return scnprintf(buf,PAGE_SIZE,"%s\n",name); +} + +static ssize_t show_enum(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + int minval,maxval,val; + const char *name; + ssize_t cnt = 0; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + minval = pvr2_hdw_get_ctl_min_value(sfp->channel.hdw,id); + maxval = pvr2_hdw_get_ctl_max_value(sfp->channel.hdw,id); + for (val = minval; val <= maxval; val++) { + name = pvr2_hdw_get_ctl_value_name(sfp->channel.hdw,id,val); + cnt += scnprintf(buf+cnt,PAGE_SIZE-cnt,"%s\n",name); + } + pvr2_sysfs_trace("pvr2_sysfs(%p) show_enum(cid=%d)",sfp,id); + return cnt; +} + +static int store_val_any(int id,struct pvr2_sysfs *sfp, + const char *buf,unsigned int count) +{ + int val,minval,maxval; + int ch,ret; + const char *nv; + unsigned int nl; + int negfl; + + /* Trim leading / trailing whitespace */ + while (count) { + ch = buf[0]; + if ((ch > 32) && (ch < 127)) break; + buf++; + count--; + } + while (count) { + ch = buf[count-1]; + if ((ch > 32) && (ch < 127)) break; + count--; + } + + /* Is this an enum? Look for a string value */ + minval = pvr2_hdw_get_ctl_min_value(sfp->channel.hdw,id); + maxval = pvr2_hdw_get_ctl_max_value(sfp->channel.hdw,id); + for (val = minval; val <= maxval; val++) { + nv = pvr2_hdw_get_ctl_value_name(sfp->channel.hdw,id,val); + if ((!nv) && (val == minval)) break; /* Not an enum */ + pvr2_sysfs_trace("pvr2_sysfs(%p) trying ctl_id %d val %d", + sfp,id,val); + if (!nv) { + pvr2_sysfs_trace("pvr2_sysfs(%p) no pointer",sfp); + continue; + } + nl = strlen(nv); + if (nl != count) { + pvr2_sysfs_trace("pvr2_sysfs(%p) count mismatch" + " %d != %d", + sfp,count,nl); + continue; + } + if (memcmp(buf,nv,nl)) { + pvr2_sysfs_trace( + "pvr2_sysfs(%p) name mismatch" + " >>%.*s<< != >>%.*s<<", + sfp,nl,buf,nl,nv); + continue; + } + pvr2_sysfs_trace("pvr2_sysfs(%p) store_val_any(cid=%d)" + " is enum %s", + sfp,id,nv); + ret = pvr2_hdw_set_ctl_value(sfp->channel.hdw,id,val); + pvr2_hdw_commit_ctl(sfp->channel.hdw); + return 0; + } + if (val > minval) { + pvr2_sysfs_trace( + "pvr2_sysfs(%p) store_val_any(cid=%d)" + " unmatched enum >>%.*s<<", + sfp,id,count,buf); + } + + /* Try to parse as a number */ + negfl = 0; + val = 0; + if (count) { + if (buf[0] == '-') { + negfl = !0; + buf++; + count--; + } else if (buf[0] == '+') { + buf++; + count--; + } + } + while (count) { + ch = buf[0]; + if ((ch < '0') || (ch > '9')) break; + val = val * 10; + val += (ch - '0'); + buf++; + count--; + } + if (!count) { + if (negfl) val = -val; + pvr2_sysfs_trace("pvr2_sysfs(%p) store_val_any(cid=%d)" + " int is %d", + sfp,id,val); + + ret = pvr2_hdw_set_ctl_value(sfp->channel.hdw,id,val); + pvr2_hdw_commit_ctl(sfp->channel.hdw); + return ret; + } + + return -EINVAL; +} + +static int store_val_multi(int id,struct pvr2_sysfs *sfp, + const char *buf,unsigned int count) +{ + unsigned int count2; + int ret; + + while (count) { + count2 = 0; + while ((count2 < count) && (buf[count2] != '\n')) count2++; + ret = store_val_any(id,sfp,buf,count2); + if (ret < 0) return ret; + if (count2 < count) count2++; + buf += count2; + count -= count2; + } + return 0; +} + +static ssize_t store_val_int(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_multi(id,sfp,buf,count); + if (!ret) ret = count; + return ret; +} + +static ssize_t store_val_enum(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_multi(id,sfp,buf,count); + if (!ret) ret = count; + return ret; +} + +/* + Mike Isely 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_int,ctl_id) \ +CREATE_SHOW_INSTANCE(show_val_enum,ctl_id) \ +CREATE_SHOW_INSTANCE(show_enum,ctl_id) \ +CREATE_STORE_INSTANCE(store_val_int,ctl_id) \ +CREATE_STORE_INSTANCE(store_val_enum,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) + + 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_val_int)(struct class_device *,char *); + ssize_t (*show_val_enum)(struct class_device *,char *); + ssize_t (*store_val_int)(struct class_device *, + const char *,size_t); + ssize_t (*store_val_enum)(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_val_int = show_val_int_##ctl_id, \ + .show_val_enum = show_val_enum_##ctl_id, \ + .store_val_int = store_val_int_##ctl_id, \ + .store_val_enum = store_val_enum_##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), +}; + + +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; + + if ((ctl_id < 0) || (ctl_id >= (sizeof(funcs)/sizeof(funcs[0])))) { + return; + } + + fp = funcs + ctl_id; + + 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->attr_id = ctl_id; + + 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_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; + + if (pvr2_hdw_get_ctl_rw(sfp->channel.hdw,ctl_id)) { + cip->attr_val.attr.mode |= S_IWUSR|S_IWGRP; + } + + cip->attr_gen[0] = &cip->attr_name.attr; + cip->attr_gen[1] = &cip->attr_val.attr; + if (pvr2_hdw_get_ctl_value_name( + sfp->channel.hdw,ctl_id, + pvr2_hdw_get_ctl_min_value(sfp->channel.hdw,ctl_id))) { + // Control is an enumeration + cip->attr_gen[2] = &cip->attr_enum.attr; + cip->attr_val.show = fp->show_val_enum; + cip->attr_val.store = fp->store_val_enum; + } else { + // Control is an integer + cip->attr_val.show = fp->show_val_int; + cip->attr_val.store = fp->store_val_int; + cip->attr_gen[2] = &cip->attr_min.attr; + cip->attr_gen[3] = &cip->attr_max.attr; + } + + cip->grp.name = item_names[ctl_id]; + cip->grp.attrs = cip->attr_gen; + + sysfs_create_group(&sfp->class_dev->kobj,&cip->grp); +} + +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; +} + + +static void pvr2_sysfs_add_controls(struct pvr2_sysfs *sfp) +{ + unsigned int ctl_id; + + for (ctl_id = 0; + ctl_id < (sizeof(item_names)/sizeof(item_names[0])); ctl_id++) { + if (!item_names[ctl_id]) continue; + pvr2_sysfs_add_control(sfp,ctl_id); + } +} + + +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; + pvr2_sysfs_tear_down_debugifc(sfp); + 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); + pvr2_sysfs_add_debugifc(sfp); +} + + +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; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16) + clp->class.hotplug = pvr2_sysfs_hotplug; +#else + clp->class.uevent = pvr2_sysfs_hotplug; +#endif + 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); +} + + +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; +} + + +/* + 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/linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.h b/linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.h new file mode 100644 index 000000000..ff9373b47 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-sysfs.h @@ -0,0 +1,47 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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 +#include +#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/linux/drivers/media/video/pvrusb2/pvrusb2-tuner.c b/linux/drivers/media/video/pvrusb2/pvrusb2-tuner.c new file mode 100644 index 000000000..f829c0acc --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-tuner.c @@ -0,0 +1,123 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 "compat.h" +#include +#include +#include + +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/linux/drivers/media/video/pvrusb2/pvrusb2-tuner.h b/linux/drivers/media/video/pvrusb2/pvrusb2-tuner.h new file mode 100644 index 000000000..556f12aa9 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-tuner.h @@ -0,0 +1,38 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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/linux/drivers/media/video/pvrusb2/pvrusb2-util.h b/linux/drivers/media/video/pvrusb2/pvrusb2-util.h new file mode 100644 index 000000000..e53aee416 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-util.h @@ -0,0 +1,63 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c new file mode 100644 index 000000000..ba99db26c --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c @@ -0,0 +1,1270 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 "compat.h" +#include +#include + +#include "pvrusb2-context.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-v4l2.h" +#include "pvrusb2-ioread.h" +#include +#include + + +#define PVR_WIDTH_DVD 720 +#define PVR_WIDTH_SVCD 480 +#define PVR_WIDTH_VCD 352 + +#define PVR_HEIGHT_PAL 480 +#define PVR_HEIGHT_NTSC 576 + +struct pvr2_v4l2_dev; +struct pvr2_v4l2_fh; +struct pvr2_v4l2; + +struct pvr2_v4l2_dev { + struct pvr2_v4l2 *v4lp; + struct video_device *vdev; + struct pvr2_context_stream *stream; + 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"); + +#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) +#define V4L2_CID_PVR_INPUT (V4L2_CID_PRIVATE_BASE+8) +#define V4L2_CID_PVR_AUDIOMODE (V4L2_CID_PRIVATE_BASE+9) +#define V4L2_CID_PVR_FREQUENCY (V4L2_CID_PRIVATE_BASE+10) +#define V4L2_CID_PVR_HRES (V4L2_CID_PRIVATE_BASE+11) +#define V4L2_CID_PVR_VRES (V4L2_CID_PRIVATE_BASE+12) + +#define V4L2_CID_PVR_MAX (V4L2_CID_PRIVATE_BASE+12) + +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} + } +#ifdef notdef + { + .index = 1, + .name = "Radio Tuner", + .type = V4L2_TUNER_RADIO, + .capability = (V4L2_TUNER_CAP_STEREO), + .rangelow = 0, + .rangehigh = 0, + .rxsubchans = 0, + .audmode = V4L2_TUNER_MODE_STEREO, + .signal = 0, + .afc = 0, + .reserved = {0,0,0,0} + } +#endif +}; + +struct v4l2_standard pvr_standards[] = { + [PVR2_CVAL_VIDEOSTANDARD_PAL_BG] = { + .id = V4L2_STD_PAL_BG, + .frameperiod = + { + .numerator = 1, + .denominator= 25 + }, + .framelines = 625, + .reserved = {0,0,0,0} + }, + [PVR2_CVAL_VIDEOSTANDARD_PAL_I] = { + .id = V4L2_STD_PAL_I, + .frameperiod = + { + .numerator = 1, + .denominator= 25 + }, + .framelines = 625, + .reserved = {0,0,0,0} + }, + [PVR2_CVAL_VIDEOSTANDARD_PAL_DK] = { + .id = V4L2_STD_PAL_DK, + .frameperiod = + { + .numerator = 1, + .denominator= 25 + }, + .framelines = 625, + .reserved = {0,0,0,0} + }, + [PVR2_CVAL_VIDEOSTANDARD_SECAM_L] = { + .id = V4L2_STD_SECAM, + .frameperiod = + { + .numerator = 1, + .denominator= 25 + }, + .framelines = 625, + .reserved = {0,0,0,0} + }, + [PVR2_CVAL_VIDEOSTANDARD_NTSC_M] = { + .id = V4L2_STD_NTSC_M, + .frameperiod = + { + .numerator = 1001, + .denominator= 30000 + }, + .framelines = 525, + .reserved = {0,0,0,0} + }, + [PVR2_CVAL_VIDEOSTANDARD_PAL_M] = { + .id = V4L2_STD_PAL_M, + .frameperiod = + { + .numerator = 1001, + .denominator= 30000 + }, + .framelines = 525, + .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 } + } + } + } +}; + +static int cnv_cid_v4l2_pvr2(int id) +{ + switch (id) { + case V4L2_CID_BRIGHTNESS: + return PVR2_CID_BRIGHTNESS; + case V4L2_CID_SATURATION: + return PVR2_CID_SATURATION; + case V4L2_CID_CONTRAST: + return PVR2_CID_CONTRAST; + case V4L2_CID_HUE: + return PVR2_CID_HUE; + case V4L2_CID_AUDIO_VOLUME: + return PVR2_CID_VOLUME; + case V4L2_CID_AUDIO_BALANCE: + return PVR2_CID_BALANCE; + case V4L2_CID_AUDIO_BASS: + return PVR2_CID_BASS; + case V4L2_CID_AUDIO_TREBLE: + return PVR2_CID_TREBLE; + case V4L2_CID_AUDIO_MUTE: + return PVR2_CID_MUTE; + case V4L2_CID_PVR_SRATE: + return PVR2_CID_SRATE; + case V4L2_CID_PVR_AUDIOBITRATE: + return PVR2_CID_AUDIOBITRATE; + case V4L2_CID_PVR_AUDIOCRC: + return PVR2_CID_AUDIOCRC; + case V4L2_CID_PVR_AUDIOEMPHASIS: + return PVR2_CID_AUDIOEMPHASIS; + case V4L2_CID_PVR_VBR: + return PVR2_CID_VBR; + case V4L2_CID_PVR_VIDEOBITRATE: + return PVR2_CID_AVERAGEVIDEOBITRATE; + case V4L2_CID_PVR_VIDEOPEAK: + return PVR2_CID_PEAKVIDEOBITRATE; + case V4L2_CID_PVR_INPUT: + return PVR2_CID_INPUT; + case V4L2_CID_PVR_AUDIOMODE: + return PVR2_CID_AUDIOMODE; + case V4L2_CID_PVR_FREQUENCY: + return PVR2_CID_FREQUENCY; + case V4L2_CID_PVR_HRES: + return PVR2_CID_HRES; + case V4L2_CID_PVR_VRES: + return PVR2_CID_VRES; + } + return -1; +} + +#ifdef notdef +static int cnv_cid_pvr2_v4l2(int id) +{ + switch (id) { + case PVR2_CID_BRIGHTNESS: + return V4L2_CID_BRIGHTNESS; + case PVR2_CID_SATURATION: + return V4L2_CID_SATURATION; + case PVR2_CID_CONTRAST: + return V4L2_CID_CONTRAST; + case PVR2_CID_HUE: + return V4L2_CID_HUE; + case PVR2_CID_VOLUME: + return V4L2_CID_AUDIO_VOLUME; + case PVR2_CID_BALANCE: + return V4L2_CID_AUDIO_BALANCE; + case PVR2_CID_BASS: + return V4L2_CID_AUDIO_BASS; + case PVR2_CID_TREBLE: + return V4L2_CID_AUDIO_TREBLE; + case PVR2_CID_MUTE: + return V4L2_CID_AUDIO_MUTE; + + return id + V4L2_CID_PRIVATE_BASE; + } + return -1; +} +#endif + +/* + * 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; + + if ((vs->index < PVR2_CVAL_VIDEOSTANDARD_MIN) || + (vs->index > PVR2_CVAL_VIDEOSTANDARD_MAX)) { + break; + } + + memcpy(vs, &pvr_standards[idx], sizeof(struct v4l2_standard)); + vs->index = idx; + strlcpy(vs->name, + pvr2_hdw_get_ctl_value_name(hdw, + PVR2_CID_VIDEOSTANDARD, + idx), + sizeof(vs->name)); + + ret = 0; + break; + } + + case VIDIOC_G_STD: + { + v4l2_std_id *vs = (v4l2_std_id *)arg; + + switch (pvr2_hdw_get_ctl_value(hdw,PVR2_CID_VIDEOSTANDARD)) { + default: + case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: + *vs = V4L2_STD_NTSC_M; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_M: + *vs = V4L2_STD_PAL_M; + break; + case PVR2_CVAL_VIDEOSTANDARD_SECAM_L: + *vs = V4L2_STD_SECAM; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_BG: + *vs = V4L2_STD_PAL_BG; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_I: + *vs = V4L2_STD_PAL_I; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_DK: + *vs = V4L2_STD_PAL_DK; + break; + } + ret = 0; + break; + } + + case VIDIOC_S_STD: + { + v4l2_std_id *vs = (v4l2_std_id *)arg; + int val = PVR2_CVAL_VIDEOSTANDARD_NTSC_M; + + if (*vs & V4L2_STD_NTSC_M){ + val = PVR2_CVAL_VIDEOSTANDARD_NTSC_M; + } else if (*vs & V4L2_STD_PAL_BG){ + val = PVR2_CVAL_VIDEOSTANDARD_PAL_BG; + } else if (*vs & V4L2_STD_PAL_I){ + val = PVR2_CVAL_VIDEOSTANDARD_PAL_I; + } else if (*vs & V4L2_STD_PAL_DK){ + val = PVR2_CVAL_VIDEOSTANDARD_PAL_DK; + } else if (*vs & V4L2_STD_SECAM){ + val = PVR2_CVAL_VIDEOSTANDARD_SECAM_L; + } else if (*vs & V4L2_STD_PAL_M){ + val = PVR2_CVAL_VIDEOSTANDARD_PAL_M; + } + + pvr2_hdw_set_ctl_value(hdw,PVR2_CID_VIDEOSTANDARD,val); + + break; + } + + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *vi = (struct v4l2_input *)arg; + struct v4l2_input tmp; + + if ((vi->index > PVR2_CVAL_INPUT_MAX) || + (vi->index < PVR2_CVAL_INPUT_MIN)) { + break; + } + + memset(&tmp,0,sizeof(tmp)); + tmp.index = vi->index; + 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; + } + + strlcpy(tmp.name, + pvr2_hdw_get_ctl_value_name(hdw,PVR2_CID_INPUT, + vi->index), + sizeof(tmp.name)); + + /* 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 v4l2_input *vi = (struct v4l2_input *)arg; + vi->index = pvr2_hdw_get_ctl_value(hdw,PVR2_CID_INPUT); + ret = 0; + break; + } + + case VIDIOC_S_INPUT: + { + struct v4l2_input *vi = (struct v4l2_input *)arg; + ret = 0; + if (pvr2_hdw_set_ctl_value(hdw,PVR2_CID_INPUT,vi->index)) { + ret = -EINVAL; + } + 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; + 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; + } + + switch (pvr2_hdw_get_ctl_value(hdw,PVR2_CID_AUDIOMODE)) { + case PVR2_CVAL_AUDIOMODE_MONO: + vt->audmode = V4L2_TUNER_MODE_MONO; + break; + case PVR2_CVAL_AUDIOMODE_STEREO: + vt->audmode = V4L2_TUNER_MODE_STEREO; + break; + case PVR2_CVAL_AUDIOMODE_LANG1: + vt->audmode = V4L2_TUNER_MODE_LANG1; + break; + case PVR2_CVAL_AUDIOMODE_LANG2: + vt->audmode = V4L2_TUNER_MODE_LANG2; + break; + case PVR2_CVAL_AUDIOMODE_SAP: + vt->audmode = V4L2_TUNER_MODE_SAP; + break; + } + + ret = 0; + break; + } + + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *vt=(struct v4l2_tuner *)arg; + int val = PVR2_CVAL_AUDIOMODE_STEREO; + + if (vt->index != 0) + break; + + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + val = PVR2_CVAL_AUDIOMODE_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + val = PVR2_CVAL_AUDIOMODE_STEREO; + break; + case V4L2_TUNER_MODE_LANG1: + val = PVR2_CVAL_AUDIOMODE_LANG1; + break; + case V4L2_TUNER_MODE_SAP: // Also LANG2 + val = PVR2_CVAL_AUDIOMODE_SAP; + break; + } + + pvr2_hdw_set_ctl_value(hdw,PVR2_CID_AUDIOMODE,val); + ret = 0; + } + + case VIDIOC_S_FREQUENCY: + { + const struct v4l2_frequency *vf = (struct v4l2_frequency *)arg; + + pvr2_hdw_set_ctl_value(hdw,PVR2_CID_FREQUENCY, + vf->frequency * 62500); + + ret = 0; + break; + } + + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *vf = (struct v4l2_frequency *)arg; + int val; + + val = pvr2_hdw_get_ctl_value(hdw,PVR2_CID_FREQUENCY); + + val /= 62500; + vf->frequency = val; + + ret = 0; + 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; + + switch(vf->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memcpy(vf, &pvr_format[PVR_FORMAT_PIX], + sizeof(struct v4l2_format)); + vf->fmt.pix.width = + pvr2_hdw_get_ctl_value(hdw,PVR2_CID_HRES); + if (pvr2_hdw_get_ctl_value(hdw,PVR2_CID_INTERLACE)) { + vf->fmt.pix.width /= 2; + } + vf->fmt.pix.height = + pvr2_hdw_get_ctl_value(hdw,PVR2_CID_VRES); + 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 = pvr2_hdw_get_ctl_value(hdw, + PVR2_CID_VIDEOSTANDARD); + switch (vd_std) { + case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: + case PVR2_CVAL_VIDEOSTANDARD_PAL_M: + hf=480; + break; + default: + hf=576; + break; + } + 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_hdw_set_ctl_value( + hdw,PVR2_CID_HRES, + vf->fmt.pix.width); + pvr2_hdw_set_ctl_value( + hdw,PVR2_CID_VRES, + vf->fmt.pix.height); + pvr2_hdw_set_ctl_value( + 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 v4l2_queryctrl *vc = (struct v4l2_queryctrl *)arg; + int pvr2_id = cnv_cid_v4l2_pvr2(vc->id); + if (pvr2_id < 0) { + ret = -EINVAL; + break; + } + + if (pvr2_hdw_get_ctl_value_name(hdw,pvr2_id,0)) { + vc->type = V4L2_CTRL_TYPE_MENU; + } else { + vc->type = V4L2_CTRL_TYPE_INTEGER; + } + strlcpy(vc->name,pvr2_hdw_get_ctl_name(hdw,pvr2_id), + sizeof(vc->name)); + vc->minimum = pvr2_hdw_get_ctl_min_value(hdw,pvr2_id); + vc->maximum = pvr2_hdw_get_ctl_max_value(hdw,pvr2_id); + vc->step = 1; + ret = 0; + break; + } + + case VIDIOC_QUERYMENU: + { + struct v4l2_querymenu *vm = (struct v4l2_querymenu *)arg; + int pvr2_id = cnv_cid_v4l2_pvr2(vm->id); + const char *value_name; + if (pvr2_id < 0) { + ret = -EINVAL; + break; + } + + value_name = pvr2_hdw_get_ctl_value_name(hdw,pvr2_id, + vm->index); + if (value_name) { + strlcpy(vm->name,value_name,sizeof(vm->name)); + ret = 0; + } else { + ret = -EINVAL; + } + + break; + } + + case VIDIOC_G_CTRL: + { + struct v4l2_control *vc = (struct v4l2_control *)arg; + int pvr2_id; + + pvr2_id = cnv_cid_v4l2_pvr2(vc->id); + if (pvr2_id < 0) { + ret = -EINVAL; + break; + } + ret = 0; + vc->value = pvr2_hdw_get_ctl_value(hdw,pvr2_id); + break; + } + + case VIDIOC_S_CTRL: + { + struct v4l2_control *vc = (struct v4l2_control *)arg; + int pvr2_id; + + pvr2_id = cnv_cid_v4l2_pvr2(vc->id); + if (pvr2_id < 0) { + ret = -EINVAL; + break; + } + + ret = pvr2_hdw_set_ctl_value(hdw,pvr2_id,vc->value); + break; + } + + case VIDIOC_LOG_STATUS: + { + pvr2_hdw_trigger_module_log(hdw); + 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 v4l video device (%s, minor=%d)", + pvr2_config_get_name(dip->config),dip->vdev->minor); + 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 video_device *vdev = video_devdata(file); + struct pvr2_v4l2_dev *dip = + (struct pvr2_v4l2_dev *)video_get_drvdata(vdev); + struct pvr2_v4l2_fh *fhp; + struct pvr2_v4l2 *vp = dip->v4lp; + struct pvr2_hdw *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_ioread_create(); + 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_ioread_setup(fh->rhp,sp); + 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) +{ +#ifdef notdef + struct usb_device *usbdev; +#endif + int mindevnum; + int unit_number; + int v4l_type; + dip->v4lp = vp; + dip->config = cfg; + +#ifdef notdef + usbdev = pvr2_hdw_get_dev(vp->channel.mc_head->hdw); +#endif + + 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)); +#ifdef notdef + /* ????? This relation may be problematic on a disconnect. Is this + really needed? I can't seem to find a reason for it. This + can't be a required thing - what if the video device being set + up doesn't have a real hardware device under it? */ + dip->vdev->dev = &usbdev->dev; +#endif + dip->vdev->release = video_device_release; + video_set_drvdata(dip->vdev,dip); + + 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 pvrusb2 v4l device, minor=%d", + dip->vdev->minor); + } + 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)); + 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/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.h b/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.h new file mode 100644 index 000000000..9a995e2d2 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.h @@ -0,0 +1,40 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * + * 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/linux/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c b/linux/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c new file mode 100644 index 000000000..d0bc3682b --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c @@ -0,0 +1,248 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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-hdw-internal.h" +#include "pvrusb2-debug.h" +#include +#include +#include +#include + +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; + int v = 0; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_input(%d)", + hdw->controls[PVR2_CID_INPUT].value); + switch(hdw->controls[PVR2_CID_INPUT].value){ + case PVR2_CVAL_INPUT_TV: + v = 4; + break; + case PVR2_CVAL_INPUT_COMPOSITE: + v = 5; + break; + case PVR2_CVAL_INPUT_SVIDEO: + v = 8; + break; + case PVR2_CVAL_INPUT_RADIO: + // ????? No idea yet what to do here + default: + return; + } + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_S_INPUT,&v); +} + + +static int check_input(struct pvr2_v4l_decoder *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + return hdw->controls[PVR2_CID_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->controls[PVR2_CID_SRATE].value); + switch (hdw->controls[PVR2_CID_SRATE].value) { + 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->controls[PVR2_CID_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_i2c_client_cmd(ctxt->client, + (fl ? VIDIOC_STREAMON : VIDIOC_STREAMOFF),0); +} + + +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/linux/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h b/linux/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h new file mode 100644 index 000000000..1c0c98efb --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h @@ -0,0 +1,53 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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 "compat.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/linux/drivers/media/video/pvrusb2/pvrusb2.h b/linux/drivers/media/video/pvrusb2/pvrusb2.h new file mode 100644 index 000000000..074533e9c --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/pvrusb2.h @@ -0,0 +1,43 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely + * Copyright (C) 2004 Aurelien Alleaume + * + * 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: *** + */ -- cgit v1.2.3 From 70d1bdb4e801ae6b0c29d2f103225c966b9eded4 Mon Sep 17 00:00:00 2001 From: Michael Krufky Date: Sun, 26 Feb 2006 01:16:54 -0400 Subject: add pvrusb2 to local v4l-dvb build From: Michael Krufky - removed 'make pvrusb2' merge command - created in-kernel Makefile, Kconfig not yet done. Signed-off-by: Michael Krufky --- linux/drivers/media/video/pvrusb2/Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 linux/drivers/media/video/pvrusb2/Makefile (limited to 'linux') diff --git a/linux/drivers/media/video/pvrusb2/Makefile b/linux/drivers/media/video/pvrusb2/Makefile new file mode 100644 index 000000000..c83742fb1 --- /dev/null +++ b/linux/drivers/media/video/pvrusb2/Makefile @@ -0,0 +1,11 @@ +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-sysfs.o pvrusb2-context.o pvrusb2-io.o \ + pvrusb2-ioread.o pvrusb2-debugifc.o + +obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2.o + +EXTRA_CFLAGS += -I$(src)/.. -- cgit v1.2.3 From 555a4680960f6297f0e4cdead5f8a48e3e3b9cfe Mon Sep 17 00:00:00 2001 From: Mike Isely Date: Sun, 26 Feb 2006 16:00:20 -0600 Subject: Log a message if a pvrusb2 I2C write is too large From: Mike Isely The wire protocol to the PVR USB2 hardware limits the total size of an I2C write transfer (among other things). The driver detects this case and fails the transfer; this change also logs the message. We need this in order to catch V4L modules which might be attempting such transfers and deal with them on a case-by-case basis. Signed-off-by: Mike Isely --- linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'linux') diff --git a/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c index e682538ef..d3b223d09 100644 --- a/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c @@ -51,7 +51,14 @@ static int pvr2_i2c_write(struct pvr2_hdw *hdw, /* Context */ #endif if (!data) length = 0; - if (length > (sizeof(hdw->cmd_buffer) - 3)) return -ENOTSUPP; + 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,(sizeof(hdw->cmd_buffer) - 3)); + return -ENOTSUPP; + } LOCK_TAKE(hdw->ctl_lock); -- cgit v1.2.3