diff options
author | Mauro Carvalho Chehab <devnull@localhost> | 2005-11-14 13:31:23 +0000 |
---|---|---|
committer | Mauro Carvalho Chehab <devnull@localhost> | 2005-11-14 13:31:23 +0000 |
commit | fcc4a84bda737ed0f6b3511d3206b927d0d8f115 (patch) | |
tree | 449c045aadbf0b43f402b6bfd6e05982e81aab0c /v4l_experimental | |
parent | af56f460fb2b3169685752f928993355a42573e3 (diff) | |
download | mediapointer-dvb-s2-fcc4a84bda737ed0f6b3511d3206b927d0d8f115.tar.gz mediapointer-dvb-s2-fcc4a84bda737ed0f6b3511d3206b927d0d8f115.tar.bz2 |
Added experimental branch for pvrusb2
From: Mike Isely at pobox <isely@pobox.com>
- Added experimental branch for pvrusb2
Signed-off-by: Mike Isely at pobox <isely@pobox.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@brturbo.com.br>
Diffstat (limited to 'v4l_experimental')
35 files changed, 9902 insertions, 0 deletions
diff --git a/v4l_experimental/pvrusb2/Kbuild b/v4l_experimental/pvrusb2/Kbuild new file mode 100644 index 000000000..3e1c3d72e --- /dev/null +++ b/v4l_experimental/pvrusb2/Kbuild @@ -0,0 +1,29 @@ + +pvrusb2-objs := \ + pvrusb2-audio.o \ + pvrusb2-encoder.o \ + pvrusb2-video.o \ + pvrusb2-eeprom.o \ + pvrusb2-tuner.o \ + pvrusb2-i2c.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 \ + $(END) + +obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2.o + +# Stuff for Emacs to see, in order to encourage consistent editing style: +# *** Local Variables: *** +# *** mode: Makefile *** +# *** fill-column: 75 *** +# *** indent-tabs-mode: nil *** +# *** End: *** + +# Similarly for vim to see: +# vim:expandtab:textwidth=75 diff --git a/v4l_experimental/pvrusb2/Makefile b/v4l_experimental/pvrusb2/Makefile new file mode 100644 index 000000000..720bf695c --- /dev/null +++ b/v4l_experimental/pvrusb2/Makefile @@ -0,0 +1,57 @@ + +# Mike Isely <isely@pobox.com> + +# This is the module build file for pvrusb2. It requires the kbuild +# system from any 2.6.x kernel. It requires the kbuild system from +# any 2.6.x kernel (but it's only been tried against kernels 2.6.10 +# and later). This WILL NOT BUILD for 2.4.x kernels. Don't even +# bother trying. Even if you were to fix this build for 2.4.x, you +# would still have to port the driver as well. Everything here +# assumes 2.6.x. + +# To build, you can just run this Makefile. There are several +# variables you may want to override however: + +# KDIR - Path to kernel source tree +# KREL - Version of kernel, i.e. 'uname -r' output +# INSTALL_MOD_DIR - where within the module tree to put the driver + +# If you do not override anything, then KREL is set to the result of +# 'uname -r', KDIR is set to '/lib/modules/$(KREL)/build', and +# INSTALL_MOD_DIR is set to 'pvrusb2'. If you choose to override +# KDIR, then you do _NOT_ need to worry about KREL, as KREL is only +# used here when calculating KDIR. If the default path for KDIR is +# only wrong in terms of version element, then you can just override +# KREL with the corrected value. + +# Sensible build targets include 'modules' (same as no target), +# 'install', and 'clean' + +ifeq ($(KERNELRELEASE),) + + # Override any of these if you'd like + ifeq ($(strip $(KREL)),) + KREL := $(shell uname -r) + endif + ifeq ($(strip $(KDIR)),) + KDIR := /lib/modules/$(KREL)/build + endif + INSTALL_MOD_DIR := pvrusb2 + + .PHONY: all default install clean modules + default: all + all: modules + + modules modules_install clean: + $(MAKE) INSTALL_MOD_DIR=$(INSTALL_MOD_DIR) -C $(KDIR) M=$(shell pwd) CONFIG_VIDEO_PVRUSB2=m $@ + + install: + $(MAKE) INSTALL_MOD_DIR=$(INSTALL_MOD_DIR) -C $(KDIR) M=$(shell pwd) CONFIG_VIDEO_PVRUSB2=m modules_install + +else + + # Backwards compatibility in case kbuild can't find Kbuild on its own. + include Kbuild + +endif + diff --git a/v4l_experimental/pvrusb2/README b/v4l_experimental/pvrusb2/README new file mode 100644 index 000000000..dc93b7750 --- /dev/null +++ b/v4l_experimental/pvrusb2/README @@ -0,0 +1,191 @@ + +$Id: README,v 1.1 2005/11/14 13:31:24 mchehab Exp $ +Mike Isely <isely@pobox.com> + + pvrusb2 driver + +Background: + + This driver is intended for the "Hauppauge WinTV PVR USB 2.0", which + is a USB 2.0 hosted TV Tuner. This driver is a work in progress. + Its history started with the reverse-engineering effort by Björn + Danielsson <pvrusb2@dax.nu> whose web page can be found here: + + http://pvrusb2.dax.nu/ + + From there Aurelien Alleaume <slts@free.fr> began an effort to + create a video4linux compatible driver. I began with Aurelien's + last known snapshot and evolved the driver to the state it is in + here. + + More information on this driver can be found at: + + http://www.isely.net/pvrusb2.html + + + This driver has a strong separation of layers. They are very + roughly: + + 1a. Low level wire-protocol implementation with the device. + + 1b. I2C adaptor implementation and corresponding I2C client drivers + implemented elsewhere in V4L. + + 1c. High level hardware driver implementation which coordinates all + activities that ensure correct operation of the device. + + 2. A "context" layer which manages instancing of driver, setup, + tear-down, arbitration, and interaction with high level + interfaces appropriately as devices are hotplugged in the + system. + + 3. High level interfaces which glue the driver to various published + Linux APIs (V4L, sysfs, maybe DVB in the future). + + The most important shearing layer is between the top 2 layers. A + lot of work went into the driver to ensure that any kind of + conceivable API can be laid on top of the core driver. (Yes, the + driver internally leverages V4L to do its work but that really has + nothing to do with the API published by the driver to the outside + world.) The architecture allows for different APIs to + simultaneously access the driver. I have a strong sense of fairness + about APIs and also feel that it is a good design principle to keep + implementation and interface isolated from each other. Thus while + right now the V4L high level interface is the most complete, the + sysfs high level interface will work equally well for similar + functions, and there's no reason I see right now why it shouldn't be + possible to produce a DVB high level interface that can sit right + alongside V4L. + + NOTE: Complete documentation on the pvrusb2 driver is contained in + the html files within the doc directory; these are exactly the same + as what is on the web site at the time. Browse those files + (especially the FAQ) before asking questions. + + +Building + + To build these modules essentially amounts to just running "Make", + but you need the kernel source tree nearby and you will likely also + want to set a few controlling environment variables first in order + to link things up with that source tree. Please see the Makefile + here for comments that explain how to do that. + + +Source file list / functional overview: + + (Note: The term "module" used below generally refers to loosely + defined functional units within the pvrusb2 driver and bears no + relation to the Linux kernel's concept of a loadable module.) + + pvrusb2-audio.[ch] - This is glue logic that resides between this + driver and the msp3400.ko I2C client driver (which is found + elsewhere in V4L). + + pvrusb2-context.[ch] - This module implements the context for an + instance of the driver. Everything else eventually ties back to + or is otherwise instanced within the data structures implemented + here. Hotplugging is ultimately coordinated here. All high level + interfaces tie into the driver through this module. This module + helps arbitrate each interface's access to the actual driver core, + and is designed to allow concurrent access through multiple + instances of multiple interfaces (thus you can for example change + the tuner's frequency through sysfs while simultaneously streaming + video through V4L out to an instance of mplayer). + + pvrusb2-debug.h - This header defines a printk() wrapper and a mask + of debugging bit definitions for the various kinds of debug + messages that can be enabled within the driver. + + pvrusb2-debugifc.[ch] - This module implements a crude command line + oriented debug interface into the driver. Aside from being part + of the process for implementing manual firmware extraction (see + the pvrusb2 web site mentioned earlier), probably I'm the only one + who has ever used this. It is mainly a debugging aid. + + pvrusb2-eeprom.[ch] - This is glue logic that resides between this + driver the tveeprom.ko module, which is itself implemented + elsewhere in V4L. + + pvrusb2-encoder.[ch] - This module implements all protocol needed to + interact with the Conexant mpeg2 encoder chip within the pvrusb2 + device. It is a crude echo of corresponding logic in ivtv, + however the design goals (strict isolation) and physical layer + (proxy through USB instead of PCI) are enough different that this + implementation had to be completely different. + + pvrusb2-hdw-internal.h - This header defines the core data structure + in the driver used to track ALL internal state related to control + of the hardware. Nobody outside of the core hardware-handling + modules should have any business using this header. All external + access to the driver should be through one of the high level + interfaces (e.g. V4L, sysfs, etc), and in fact even those high + level interfaces are restricted to the API defined in + pvrusb2-hdw.h and NOT this header. + + pvrusb2-hdw.h - This header defines the full internal API for + controlling the hardware. High level interfaces (e.g. V4L, sysfs) + will work through here. + + pvrusb2-hdw.c - This module implements all the various bits of logic + that handle overall control of a specific pvrusb2 device. + (Policy, instantiation, and arbitration of pvrusb2 devices fall + within the jurisdiction of pvrusb-context not here). + + pvrusb2-i2c.[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 here 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. + + 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 + diff --git a/v4l_experimental/pvrusb2/pvrusb2-audio.c b/v4l_experimental/pvrusb2/pvrusb2-audio.c new file mode 100644 index 000000000..ebe1f38f1 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-audio.c @@ -0,0 +1,230 @@ +/* + * + * $Id: pvrusb2-audio.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-i2c.h" +#include "pvrusb2-audio.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include "msp3400.h" +#include <linux/videodev.h> +#include <media/audiochip.h> + +/* + + MCI <isely@pobox.com> 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; +} + +/* Relay the video standard into the msp3400 module so that it can use + that information as additional clue(s) for correctly detecting + audio. */ +int pvr2_audio_set_standard(struct pvr2_hdw *hdw) +{ + struct video_channel vc; + memset(&vc,0,sizeof(vc)); + switch (hdw->controls[PVR2_CID_VIDEOSTANDARD].value) { + default: + case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: + vc.norm = VIDEO_MODE_NTSC; + break; + case PVR2_CVAL_VIDEOSTANDARD_SECAM_L: + vc.norm = VIDEO_MODE_SECAM; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_BG: + case PVR2_CVAL_VIDEOSTANDARD_PAL_I: + case PVR2_CVAL_VIDEOSTANDARD_PAL_DK: + vc.norm = VIDEO_MODE_PAL; + break; + } + hdw->subsys_enabled_mask |= PVR2_SUBSYS_AUDIO_CFG_STD; + return pvr2_i2c_msp3400_cmd(hdw,VIDIOCSCHAN,&vc); +} + +/* This function selects the correct audio input source */ +int pvr2_audio_set_stereo(struct pvr2_hdw *hdw) +{ + int stat; + unsigned short sarg = 0; + struct msp_matrix mspm; + + pvr2_trace(PVR2_TRACE_AUDIO,"pvr_audio_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); + stat = pvr2_i2c_msp3400_cmd(hdw,VIDIOC_S_TUNER,&vt); + if (stat < 0) return stat; + } + + 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; + } + stat = pvr2_i2c_msp3400_cmd(hdw,AUDC_SET_INPUT,&sarg); + if (stat < 0) return stat; + + /* 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; + stat = pvr2_i2c_msp3400_cmd(hdw,MSP_SET_MATRIX,&mspm); + if (stat < 0) return stat; + + hdw->subsys_enabled_mask |= PVR2_SUBSYS_AUDIO_CFG_MODE; + + return 0; +} + +/* This sets the audio volume parameters */ +int pvr2_audio_setvolume(struct pvr2_hdw *hdw) +{ + struct video_audio vt; + memset(&vt,0,sizeof(vt)); + vt.flags = (VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BALANCE | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE | + VIDEO_AUDIO_MUTABLE); + if (hdw->controls[PVR2_CID_MUTE].value) vt.flags |= VIDEO_AUDIO_MUTE; + vt.volume = hdw->controls[PVR2_CID_VOLUME].value; + vt.balance = hdw->controls[PVR2_CID_BALANCE].value; + vt.bass = hdw->controls[PVR2_CID_BASS].value; + vt.treble = hdw->controls[PVR2_CID_TREBLE].value; + pvr2_trace(PVR2_TRACE_AUDIO, + "pvr_audio_setvolume(vol=%d bal=%d bas=%d treb=%d mute=%d)", + vt.volume,vt.balance,vt.bass,vt.treble, + (vt.flags & VIDEO_AUDIO_MUTE) != 0); + + hdw->subsys_enabled_mask |= PVR2_SUBSYS_AUDIO_CFG_VBBTM; + + return pvr2_i2c_msp3400_cmd(hdw,VIDIOCSAUDIO,&vt); +} + +/* This reads back the current volume parameters and signal type */ +int pvr2_audio_update_status(struct pvr2_hdw *hdw) +{ + struct video_audio vt; + int stat; + + stat = pvr2_i2c_msp3400_cmd(hdw,VIDIOCGAUDIO,&vt); + if (stat < 0) return stat; + +#ifdef notdef + if (vt.flags & VIDEO_AUDIO_MUTABLE) { + hdw->controls[PVR2_CID_MUTE].value = + (vt.flags & VIDEO_AUDIO_MUTE) ? 1 : 0; + } + if (vt.flags & VIDEO_AUDIO_VOLUME) { + pvr2_trace(PVR2_TRACE_AUDIO, + "pvr_audio_update_status: got volume"); + hdw->controls[PVR2_CID_VOLUME].value = vt.volume; + } + if (vt.flags & VIDEO_AUDIO_BASS) { + pvr2_trace(PVR2_TRACE_AUDIO, + "pvr_audio_update_status: got bass"); + hdw->controls[PVR2_CID_BASS].value = vt.bass; + } + if (vt.flags & VIDEO_AUDIO_TREBLE) { + pvr2_trace(PVR2_TRACE_AUDIO, + "pvr_audio_update_status: got treble"); + hdw->controls[PVR2_CID_TREBLE].value = vt.treble; + } + if (vt.flags & (VIDEO_AUDIO_BALANCE|VIDEO_AUDIO_VOLUME)) { + pvr2_trace(PVR2_TRACE_AUDIO, + "pvr_audio_update_status: got balance"); + hdw->controls[PVR2_CID_BALANCE].value = vt.balance; + } +#endif + + hdw->flag_stereo = (vt.mode & VIDEO_SOUND_STEREO) != 0; + hdw->flag_bilingual = (vt.mode & + (VIDEO_SOUND_LANG1|VIDEO_SOUND_LANG2)) != 0; + 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/v4l_experimental/pvrusb2/pvrusb2-audio.h b/v4l_experimental/pvrusb2/pvrusb2-audio.h new file mode 100644 index 000000000..3800438f7 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-audio.h @@ -0,0 +1,52 @@ +/* + * + * $Id: pvrusb2-audio.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_AUDIO_H +#define __PVRUSB2_AUDIO_H + +/* + + This module connects the pvrusb2 driver to the I2C chip level + driver which handles device audio processing. This interface is + used internally by the driver; higher level code should only + interact through the interface provided by pvrusb2-hdw.h. + +*/ + +struct pvr2_hdw; + +int pvr2_audio_setvolume(struct pvr2_hdw *); +int pvr2_audio_set_stereo(struct pvr2_hdw *); +int pvr2_audio_set_standard(struct pvr2_hdw *); +int pvr2_audio_update_status(struct pvr2_hdw *); + +#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/v4l_experimental/pvrusb2/pvrusb2-context.c b/v4l_experimental/pvrusb2/pvrusb2-context.c new file mode 100644 index 000000000..b0faa9a44 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-context.c @@ -0,0 +1,195 @@ +/* + * $Id: pvrusb2-context.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-context.h" +#include "pvrusb2-io.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/semaphore.h> + + +static void pvr2_context_destroy(struct pvr2_context *mp) +{ + if (mp->hdw) pvr2_hdw_destroy(mp->hdw); + pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr_main id=%p",mp); + flush_workqueue(mp->workqueue); + destroy_workqueue(mp->workqueue); + kfree(mp); +} + + +static void pvr2_context_setup(struct pvr2_context *mp) +{ + pvr2_context_enter(mp); do { + (mp->kthread_ref_count)--; + if (!pvr2_hdw_dev_ok(mp->hdw)) break; + pvr2_hdw_setup(mp->hdw); + 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; + init_MUTEX(&mp->sem); + mp->hdw = pvr2_hdw_create(intf); + if (!mp->hdw) { + pvr2_context_destroy(mp); + mp = 0; + goto done; + } + + mp->workqueue = create_singlethread_workqueue("pvrusb2"); + (mp->kthread_ref_count)++; + INIT_WORK(&mp->workinit,(void (*)(void*))pvr2_context_setup,mp); + queue_work(mp->workqueue,&mp->workinit); + done: + return mp; +} + + +void pvr2_context_enter(struct pvr2_context *mp) +{ + down(&mp->sem); + 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) || + mp->kthread_ref_count)) { + destroy_flag = !0; + } + pvr2_trace(PVR2_TRACE_CREG,"pvr2_context_exit(id=%p) outside",mp); + up(&mp->sem); + 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/v4l_experimental/pvrusb2/pvrusb2-context.h b/v4l_experimental/pvrusb2/pvrusb2-context.h new file mode 100644 index 000000000..230c2828f --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-context.h @@ -0,0 +1,89 @@ +/* + * $Id: pvrusb2-context.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_BASE_H +#define __PVRUSB2_BASE_H + +#include <asm/semaphore.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +struct pvr2_hdw; /* hardware interface - defined elsewhere */ +struct pvr2_stream; /* stream interface - defined elsewhere */ + +struct pvr2_context; /* All central state */ +struct pvr2_channel; /* One I/O pathway to a user */ +struct pvr2_context_stream; /* Wrapper for a stream */ +struct pvr2_crit_reg; /* Critical region pointer */ + +struct pvr2_context_stream { + struct pvr2_channel *user; + struct pvr2_stream *stream; +}; + +struct pvr2_context { + struct pvr2_channel *mc_first; + struct pvr2_channel *mc_last; + struct pvr2_hdw *hdw; + struct pvr2_context_stream video_stream; + struct semaphore sem; + int disconnect_flag; + int kthread_ref_count; + + /* 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 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/v4l_experimental/pvrusb2/pvrusb2-debug.h b/v4l_experimental/pvrusb2/pvrusb2-debug.h new file mode 100644 index 000000000..f970be2c2 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-debug.h @@ -0,0 +1,63 @@ +/* + * $Id: pvrusb2-debug.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_DEBUG_H +#define __PVRUSB2_DEBUG_H + +extern int debug; + +#define pvr2_trace(msk, fmt, arg...) do {if(msk & 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 0x00000001 // Normal messages +#define PVR2_TRACE_ERROR_LEGS 0x00000002 // error messages +#define PVR2_TRACE_TRAP 0x00000004 // Trap & report misbehavior from app +#define PVR2_TRACE_INIT 0x00000008 // misc initialization steps +#define PVR2_TRACE_START_STOP 0x00000010 // Streaming start / stop +#define PVR2_TRACE_CTL 0x00000020 // commit of control changes +#define PVR2_TRACE_DEBUG 0x00000040 // Temporary debug code +#define PVR2_TRACE_EEPROM 0x00000080 // eeprom parsing / report +#define PVR2_TRACE_STRUCT 0x00000100 // internal struct creation +#define PVR2_TRACE_OPEN_CLOSE 0x00000200 // application open / close +#define PVR2_TRACE_CREG 0x00000400 // Main critical region entry / exit +#define PVR2_TRACE_SYSFS 0x00000800 // Sysfs driven I/O +#define PVR2_TRACE_FIRMWARE 0x00001000 // firmware upload actions +#define PVR2_TRACE_TUNER 0x00002000 // tuner operation +#define PVR2_TRACE_I2C 0x00004000 // I2C related stuff +#define PVR2_TRACE_V4LIOCTL 0x00008000 // v4l ioctl details +#define PVR2_TRACE_AUDIO 0x00010000 // audio operation +#define PVR2_TRACE_DECODER 0x00020000 // video capture operation +#define PVR2_TRACE_ENCODER 0x00040000 // mpeg2 encoder operation +#define PVR2_TRACE_BUF_POOL 0x00080000 // Track buffer pool management +#define PVR2_TRACE_BUF_FLOW 0x00100000 // Track buffer flow in system +#define PVR2_TRACE_DATA_FLOW 0x00200000 // Track data flow +#define PVR2_TRACE_DEBUGIFC 0x00400000 // Debug interface actions +#define PVR2_TRACE_GPIO 0x00800000 // 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/v4l_experimental/pvrusb2/pvrusb2-debugifc.c b/v4l_experimental/pvrusb2/pvrusb2-debugifc.c new file mode 100644 index 000000000..a29bafb85 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-debugifc.c @@ -0,0 +1,480 @@ +/* + * + * $Id: pvrusb2-debugifc.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/string.h> +#include <linux/slab.h> +#include "pvrusb2-debugifc.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" + +struct debugifc_mask_item { + const char *name; + unsigned long msk; +}; + +static struct debugifc_mask_item mask_items[] = { + {"ENC_FIRMWARE",PVR2_SUBSYS_ENC_FIRMWARE}, + {"TUN_STD",PVR2_SUBSYS_TUNER_CFG_STD}, + {"TUN_FREQ",PVR2_SUBSYS_TUNER_CFG_FREQ}, + {"AUD_VBBTM",PVR2_SUBSYS_AUDIO_CFG_VBBTM}, + {"AUD_STD",PVR2_SUBSYS_AUDIO_CFG_STD}, + {"AUD_MODE",PVR2_SUBSYS_AUDIO_CFG_MODE}, + {"DIG_NORM",PVR2_SUBSYS_DIGITIZER_CFG_NORM}, + {"DIG_INPUT",PVR2_SUBSYS_DIGITIZER_CFG_INPUT}, + {"DIG_SIZE",PVR2_SUBSYS_DIGITIZER_CFG_SIZE}, + {"DIG_AUDIO",PVR2_SUBSYS_DIGITIZER_CFG_AUDIO}, + {"DIG_BCSH",PVR2_SUBSYS_DIGITIZER_CFG_BCSH}, + {"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; + + 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/v4l_experimental/pvrusb2/pvrusb2-debugifc.h b/v4l_experimental/pvrusb2/pvrusb2-debugifc.h new file mode 100644 index 000000000..59f4373d6 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-debugifc.h @@ -0,0 +1,53 @@ +/* + * + * $Id: pvrusb2-debugifc.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_DEBUGIFC_H +#define __PVRUSB2_DEBUGIFC_H + +struct pvr2_hdw; + +/* Non-intrusively print some useful debugging info from inside the + driver. This should work even if the driver appears to be + wedged. */ +int pvr2_debugifc_print_info(struct pvr2_hdw *, + char *buf_ptr,unsigned int buf_size); + +/* Print general status of driver. This will also trigger a probe of + the USB link. Unlike print_info(), this one synchronizes with the + driver so the information should be self-consistent (but it will + hang if the driver is wedged). */ +int pvr2_debugifc_print_status(struct pvr2_hdw *, + char *buf_ptr,unsigned int buf_size); + +/* Parse a string command into a driver action. */ +int pvr2_debugifc_docmd(struct pvr2_hdw *, + const char *buf_ptr,unsigned int buf_size); + +#endif /* __PVRUSB2_DEBUGIFC_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/v4l_experimental/pvrusb2/pvrusb2-eeprom.c b/v4l_experimental/pvrusb2/pvrusb2-eeprom.c new file mode 100644 index 000000000..e83ef0db7 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-eeprom.c @@ -0,0 +1,329 @@ +/* + * + * $Id: pvrusb2-eeprom.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-eeprom.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#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. + + 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. + + 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 call) is the default choice, but if you want to try method + #1, comment out the PVRUSB2_EEPROM_DIRECT_CALL macro 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... + + */ + +#define PVRUSB2_EEPROM_DIRECT_CALL + + +#ifndef PVRUSB2_EEPROM_DIRECT_CALL + +#include "pvrusb2-i2c.h" + +/* Use I2C client interface to retrieve usable information from within + * tveeprom. This tends to be more stable than directly calling. */ + +int pvr2_eeprom_analyze(struct pvr2_hdw *hdw) +{ + u32 props[5]; + int stat; + + stat = pvr2_i2c_tveeprom_cmd(hdw,0,props); + if (stat < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to retrieve eeprom data stat=%d",stat); + return stat; + } + + trace_eeprom("eeprom client query results:"); + trace_eeprom("tuner_type=%d",props[0]); + trace_eeprom("tuner_formats=0x%x",props[1]); + trace_eeprom("model=%d",props[2]); + trace_eeprom("revision=%d",props[3]); + trace_eeprom("has_radio=%d",props[4]); + + hdw->tuner_type = props[0]; + hdw->video_standards = props[1]; + + return 0; +} + +#endif + +#ifdef PVRUSB2_EEPROM_DIRECT_CALL + +/* Directly call eeprom analysis function within tveeprom. This has + portability issues because the internal API has been changing. We + have to do something positively gross here. The two variants of + tveeprom that we deal with (ivtv and v4l) use completely different + definitions for the tveeprom structure. To accomodate this, we'll + allocate enough storage for the larger of the two, initialize it to + bad but predictable data, and then call the analysis function. + Upon return, we'll check how much data was changed and use that as + a hint to determine exactly which tveeprom structure had been + used. Did I say this was ugly? It's disgusting. */ + +#define PVR_EEPROM_I2C_ADDR 0x50 + +#include <media/tveeprom.h> + +/* + * 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 + */ + +/* We seem to only be interested in the back half of the EEPROM */ +#define EEPROM_SIZE 128 +#define EEPROM_OFFS 128 + +/* This has to be an EXACT(!!) match with the tveeprom structure + defined in our local copy of tveeprom.c. */ +struct tveeprom_ivtv { + u32 has_radio; + + u32 tuner_type; + u32 tuner_formats; + + u32 digitizer; + u32 digitizer_formats; + + u32 audio_processor; + u32 decoder_processor; + /* a_p_fmts? */ + + u32 model; + u32 revision; + u32 serial_number; + char rev_str[5]; +}; + + +int pvr2_eeprom_analyze(struct pvr2_hdw *hdw) +{ + u8 *eeprom; + u8 offs; + unsigned pcnt,tcnt; + int ret; + int tp; + struct i2c_msg msg[2]; + union { + struct tveeprom v4l; + struct tveeprom_ivtv ivtv; + } tvdata; + + memset(&tvdata,0x93,sizeof(tvdata)); + + eeprom = kmalloc(EEPROM_SIZE,GFP_KERNEL); + if (!eeprom) { + return -ENOMEM; + } + + msg[0].addr = PVR_EEPROM_I2C_ADDR; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &offs; + msg[1].addr = PVR_EEPROM_I2C_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 + EEPROM_OFFS; + msg[1].len = pcnt; + msg[1].buf = eeprom+tcnt; + if ((ret = i2c_transfer( + &hdw->i2c_adap, + msg,sizeof(msg)/sizeof(msg[0]))) != 2) { + trace_eeprom("eeprom fetch set offs err=%d",ret); + if (ret > 0) ret = -EIO; + kfree(eeprom); + return ret; + } + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14) + { + struct i2c_client fake_client; + /* Newer version expects a useless client interface */ + fake_client.addr = PVR_EEPROM_I2C_ADDR; + fake_client.adapter = &hdw->i2c_adap; + tveeprom_hauppauge_analog(&fake_client, + (struct tveeprom *)&tvdata,eeprom); + } +#else + tveeprom_hauppauge_analog((struct tveeprom *)&tvdata,eeprom); +#endif + + /* Now figure out which structure was used */ + for (tcnt = 0; tcnt < sizeof(tvdata); tcnt++) { + if (((unsigned char *)(&tvdata))[sizeof(tvdata) - (tcnt+1)] != + 0x93) { + break; + } + } + tcnt = sizeof(tvdata) - tcnt; + + if (sizeof(tvdata.ivtv) < sizeof(tvdata.v4l)) { + tp = 0; + if (tcnt > sizeof(tvdata.ivtv)) tp = 1; + } else { + tp = 1; + if (tcnt > sizeof(tvdata.v4l)) tp = 0; + } + + if (tp) { + /* v4l */ + trace_eeprom("eeprom detected v4l tveeprom module"); + trace_eeprom("eeprom direct call results:"); + trace_eeprom("has_radio=%d",tvdata.v4l.has_radio); + trace_eeprom("tuner_type=%d",tvdata.v4l.tuner_type); + trace_eeprom("tuner_formats=0x%x",tvdata.v4l.tuner_formats); + trace_eeprom("audio_processor=%d",tvdata.v4l.audio_processor); + trace_eeprom("model=%d",tvdata.v4l.model); + trace_eeprom("revision=%d",tvdata.v4l.revision); + trace_eeprom("serial_number=%d",tvdata.v4l.serial_number); + trace_eeprom("rev_str=%s",tvdata.v4l.rev_str); + hdw->tuner_type = tvdata.v4l.tuner_type; + hdw->serial_number = tvdata.v4l.serial_number; + hdw->video_standards = tvdata.v4l.tuner_formats; + } else { + /* ivtv */ + trace_eeprom("eeprom detected ivtv tveeprom module"); + trace_eeprom("eeprom direct call results:"); + trace_eeprom("has_radio=%d",tvdata.ivtv.has_radio); + trace_eeprom("tuner_type=%d",tvdata.ivtv.tuner_type); + trace_eeprom("tuner_formats=0x%x",tvdata.ivtv.tuner_formats); + trace_eeprom("audio_processor=%d",tvdata.ivtv.audio_processor); + trace_eeprom("model=%d",tvdata.ivtv.model); + trace_eeprom("revision=%d",tvdata.ivtv.revision); + trace_eeprom("serial_number=%d",tvdata.ivtv.serial_number); + trace_eeprom("rev_str=%s",tvdata.ivtv.rev_str); + hdw->tuner_type = tvdata.ivtv.tuner_type; + hdw->serial_number = tvdata.ivtv.serial_number; + hdw->video_standards = tvdata.ivtv.tuner_formats; + } + + kfree(eeprom); + + return 0; +} +#endif + + +static v4l2_std_id std_choices[] = { + [PVR2_CVAL_VIDEOSTANDARD_NTSC_M] = V4L2_STD_NTSC, + [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, +}; + +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/v4l_experimental/pvrusb2/pvrusb2-eeprom.h b/v4l_experimental/pvrusb2/pvrusb2-eeprom.h new file mode 100644 index 000000000..edc80b62e --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-eeprom.h @@ -0,0 +1,41 @@ +/* + * + * $Id: pvrusb2-eeprom.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_EEPROM_H +#define __PVRUSB2_EEPROM_H + +struct pvr2_hdw; + +int pvr2_eeprom_analyze(struct pvr2_hdw *); +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/v4l_experimental/pvrusb2/pvrusb2-encoder.c b/v4l_experimental/pvrusb2/pvrusb2-encoder.c new file mode 100644 index 000000000..450d14209 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-encoder.c @@ -0,0 +1,570 @@ +/* + * + * $Id: pvrusb2-encoder.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/device.h> // for linux/firmware.h +#include <linux/firmware.h> +#include "pvrusb2-util.h" +#include "pvrusb2-encoder.h" +#include "pvrusb2-i2c.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 API commands - definitions found from ivtv */ +#ifdef notdef +#define IVTV_API_DEC_PING_FW 0x00 +#define IVTV_API_DEC_START_PLAYBACK 0x01 +#define IVTV_API_DEC_STOP_PLAYBACK 0x02 +#define IVTV_API_DEC_PLAYBACK_SPEED 0x03 +#define IVTV_API_DEC_STEP_VIDEO 0x05 +#define IVTV_API_DEC_DMA_BLOCKSIZE 0x08 +#define IVTV_API_DEC_XFER_INFO 0x09 +#define IVTV_API_DEC_DMA_STATUS 0x0a +#define IVTV_API_DEC_DMA_FROM_HOST 0x0b +#define IVTV_API_DEC_PAUSE_PLAYBACK 0x0d +#define IVTV_API_DEC_HALT_FW 0x0f + +#define IVTV_API_DEC_DISP_STANDARD 0x10 +#define IVTV_API_DEC_GETVER 0x11 +#define IVTV_API_DEC_STREAM_INPUT 0x14 +#define IVTV_API_DEC_TIMING_INFO 0x15 +#define IVTV_API_DEC_SELECT_AUDIO 0x16 +#define IVTV_API_DEC_EVENT_NOTIFICATION 0x17 +#define IVTV_API_DEC_DISPLAY_BUFFERS 0x18 +#define IVTV_API_DEC_EXTRACT_VBI 0x19 +#define IVTV_API_DEC_DECODE_SOURCE 0x1a +#define IVTV_API_DEC_AUDIO_OUTPUT 0x1b +#define IVTV_API_DEC_SET_AV_DELAY 0x1c +#define IVTV_API_DEC_BUFFER 0x1e + +#define IVTV_API_FB_GET_FRAMEBUFFER 0x41 +#define IVTV_API_FB_GET_PIXEL_FORMAT 0x42 +#define IVTV_API_FB_SET_PIXEL_FORMAT 0x43 +#define IVTV_API_FB_GET_STATE 0x44 +#define IVTV_API_FB_SET_STATE 0x45 +#define IVTV_API_FB_GET_OSD_COORDS 0x46 +#define IVTV_API_FB_SET_OSD_COORDS 0x47 +#define IVTV_API_FB_GET_SCREEN_COORDS 0x48 +#define IVTV_API_FB_SET_SCREEN_COORDS 0x49 +#define IVTV_API_FB_GET_GLOBAL_ALPHA 0x4a +#define IVTV_API_FB_SET_GLOBAL_ALPHA 0x4b +#define IVTV_API_FB_SET_BLEND_COORDS 0x4c +#define IVTV_API_FB_GET_FLICKER_STATE 0x4f + +#define IVTV_API_FB_SET_FLICKER_STATE 0x50 +#define IVTV_API_FB_BLT_COPY 0x52 +#define IVTV_API_FB_BLT_FILL 0x53 +#define IVTV_API_FB_BLT_TEXT 0x54 +#define IVTV_API_FB_SET_FRAMEBUFFER_WINDOW 0x56 + +#define IVTV_API_FB_SET_CHROMA_KEY 0x60 +#define IVTV_API_FB_GET_ALPHA_CONTENT_INDEX 0x61 +#define IVTV_API_FB_SET_ALPHA_CONTENT_INDEX 0x62 +#endif + +#define IVTV_API_ENC_PING_FW 0x80 +#define IVTV_API_BEGIN_CAPTURE 0x81 +#define IVTV_API_END_CAPTURE 0x82 +#define IVTV_API_ASSIGN_FRAMERATE 0x8f + +#define IVTV_API_ASSIGN_FRAME_SIZE 0x91 +#define IVTV_API_ASSIGN_BITRATES 0x95 +#define IVTV_API_ASSIGN_GOP_PROPERTIES 0x97 +#define IVTV_API_ASSIGN_ASPECT_RATIO 0x99 +#define IVTV_API_ASSIGN_DNR_FILTER_MODE 0x9b +#define IVTV_API_ASSIGN_DNR_FILTER_PROPS 0x9d +#define IVTV_API_ASSIGN_CORING_LEVELS 0x9f + +#define IVTV_API_ASSIGN_SPATIAL_FILTER_TYPE 0xa1 + +#define IVTV_API_ASSIGN_3_2_PULLDOWN 0xb1 +#define IVTV_API_SELECT_VBI_LINE 0xb7 +#define IVTV_API_ASSIGN_STREAM_TYPE 0xb9 +#define IVTV_API_ASSIGN_OUTPUT_PORT 0xbb +#define IVTV_API_ASSIGN_AUDIO_PROPERTIES 0xbd + +#define IVTV_API_ENC_HALT_FW 0xc3 +#define IVTV_API_ENC_GETVER 0xc4 +#define IVTV_API_ASSIGN_GOP_CLOSURE 0xc5 +#define IVTV_API_ASSIGN_PGM_INDEX_INFO 0xc7 +#define IVTV_API_CONFIG_VBI 0xc8 +#define IVTV_API_ASSIGN_DMA_BLOCKLEN 0xc9 +#define IVTV_API_SCHED_DMA_TO_HOST 0xcc +#define IVTV_API_INITIALIZE_INPUT 0xcd + +#define IVTV_API_ASSIGN_FRAME_DROP_RATE 0xd0 +#define IVTV_API_PAUSE_ENCODER 0xd2 +#define IVTV_API_REFRESH_INPUT 0xd3 +#define IVTV_API_EVENT_NOTIFICATION 0xd5 +#define IVTV_API_ASSIGN_NUM_VSYNC_LINES 0xd6 +#define IVTV_API_ASSIGN_PLACEHOLDER 0xd8 +#define IVTV_API_MUTE_VIDEO 0xd9 +#define IVTV_API_MUTE_AUDIO 0xda + +#define IVTV_API_OSD_DMA_FROM_HOST 0xff + +/* 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 is_ntsc = (hdw->controls[PVR2_CID_VIDEOSTANDARD].value == + PVR2_CVAL_VIDEOSTANDARD_NTSC_M); + 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; + + pvr2_trace(PVR2_TRACE_ENCODER,"pvr2_encoder_configure"); + + /* set stream output port. */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_OUTPUT_PORT, 2, + 0x01, 0x01); + + /* set the Program Index Information. We want I,P,B frames (max 400) */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_PGM_INDEX_INFO, 2, + 0x07, 0x0190); + + /* NOTE : windows driver sends some 0xdc */ + + /* Mike Isely <isely@pobox.com> 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, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 2, + 0x0120, 0x0120); + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 2, + 0x0131, 0x0131); +#endif + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 2, + 0xf0, 0xf0); + + /* setup firmware to notify us about some events (don't know why...) */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_EVENT_NOTIFICATION, 4, + 0, 0, 0x10000000, 0xffffffff); + + /* set fps to 25 or 30 (1 or 0)*/ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_FRAMERATE, 1, + is_ntsc ? 0 : 1); + + /* set encoding resolution */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_FRAME_SIZE, 2, + (height_full ? height : (height / 2)), + width); + /* set encoding aspect ratio to 4:3 */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_ASPECT_RATIO, 1, + 0x02); + + /* VBI */ + + if (hdw->config == pvr2_config_vbi) { + int lines = 2 * (is_ntsc ? 12 : 18); + int size = (4*((lines*1443+3)/4)) / lines; + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_CONFIG_VBI, 7, + 0xbd05, 1, 4, + 0x25256262, 0x387f7f7f, + lines , size); +// 0x25256262, 0x13135454, lines , size); + /* select vbi lines */ +#define line_used(l) (is_ntsc ? (l >= 10 && l <= 21) : (l >= 6 && l <= 23)) + for (i = 2 ; i <= 24 ; i++){ + ret |= pvr2_write_encoder_vcmd( + hdw,IVTV_API_SELECT_VBI_LINE, 5, + i-1,line_used(i), 0, 0, 0); + ret |= pvr2_write_encoder_vcmd( + hdw,IVTV_API_SELECT_VBI_LINE, 5, + (i-1) | (1 << 31), + line_used(i), 0, 0, 0); + } + } else { + ret |= pvr2_write_encoder_vcmd( + hdw,IVTV_API_SELECT_VBI_LINE, 5, + 0xffffffff,0,0,0,0); + } + + /* set stream type, depending on resolution. */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_STREAM_TYPE, 1, + height_full ? 0x0a : 0x0b); + /* set video bitrate */ + ret |= pvr2_write_encoder_vcmd( + hdw, IVTV_API_ASSIGN_BITRATES, 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, IVTV_API_ASSIGN_GOP_PROPERTIES, 2, + is_ntsc ? 0x0f : 0x0c, 0x03); + + /* enable 3:2 pulldown */ + ret |= pvr2_write_encoder_vcmd(hdw,IVTV_API_ASSIGN_3_2_PULLDOWN,1,0); + + /* set GOP open/close property (open) */ + ret |= pvr2_write_encoder_vcmd(hdw,IVTV_API_ASSIGN_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,IVTV_API_ASSIGN_AUDIO_PROPERTIES,1, + audio); + + /* set dynamic noise reduction filter to manual, Horiz/Vert */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_DNR_FILTER_MODE, 2, + 0, 0x03); + + /* dynamic noise reduction filter param */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_DNR_FILTER_PROPS, 2 + , 0, 0); + + /* dynamic noise reduction median filter */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_CORING_LEVELS, 4, + 0, 0xff, 0, 0xff); + + /* spacial prefiler parameter */ + ret |= pvr2_write_encoder_vcmd(hdw, + IVTV_API_ASSIGN_SPATIAL_FILTER_TYPE, 2, + 0x01, 0x01); + + /* initialize video input */ + ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_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,IVTV_API_BEGIN_CAPTURE,2, + 0x01,0x14); + } else if (hdw->config == pvr2_config_mpeg) { + status = pvr2_write_encoder_vcmd(hdw,IVTV_API_BEGIN_CAPTURE,2, + 0,0x13); + } else { + status = pvr2_write_encoder_vcmd(hdw,IVTV_API_BEGIN_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,IVTV_API_END_CAPTURE,3, + 0x01,0x01,0x14); + } else if (hdw->config == pvr2_config_mpeg) { + status = pvr2_write_encoder_vcmd(hdw,IVTV_API_END_CAPTURE,3, + 0x01,0,0x13); + } else { + status = pvr2_write_encoder_vcmd(hdw,IVTV_API_END_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/v4l_experimental/pvrusb2/pvrusb2-encoder.h b/v4l_experimental/pvrusb2/pvrusb2-encoder.h new file mode 100644 index 000000000..4a35e8ac0 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-encoder.h @@ -0,0 +1,42 @@ +/* + * + * $Id: pvrusb2-encoder.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_ENCODER_H +#define __PVRUSB2_ENCODER_H + +struct pvr2_hdw; + +int pvr2_encoder_configure(struct pvr2_hdw *); +int pvr2_encoder_start(struct pvr2_hdw *); +int pvr2_encoder_stop(struct pvr2_hdw *); + +#endif /* __PVRUSB2_ENCODER_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/v4l_experimental/pvrusb2/pvrusb2-hdw-internal.h b/v4l_experimental/pvrusb2/pvrusb2-hdw-internal.h new file mode 100644 index 000000000..ad5cc0731 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-hdw-internal.h @@ -0,0 +1,159 @@ +/* + * + * $Id: pvrusb2-hdw-internal.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_HDW_INTERNAL_H +#define __PVRUSB2_HDW_INTERNAL_H + +/* + + This header sets up all the internal structures and definitions needed to + track and coordinate the driver's interaction with the hardware. ONLY + source files which actually implement part of that whole circus should be + including this header. Higher levels, like the external layers to the + various public APIs (V4L, sysfs, etc) should NOT ever include this + private, internal header. This means that pvrusb2-hdw, pvrusb2-encoder, + etc will include this, but pvrusb2-v4l should not. + +*/ + +#include "compat.h" +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <asm/semaphore.h> +#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 { down(&x##_sem); x##_held = !0; } while (0) +#define LOCK_GIVE(x) do { x##_held = 0; up(&x##_sem); } while (0) + +struct pvr2_ctl_state { + int value; + int dirty; +}; + +/* 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 */ + struct semaphore big_lock_sem; + int big_lock_held; /* For debugging */ + + char name[32]; + + /* I2C stuff */ + struct i2c_adapter i2c_adap; + struct i2c_algorithm i2c_algo; + int i2c_linked; + struct i2c_client *i2c_tuner_client; + struct i2c_client *i2c_audio_client; + struct i2c_client *i2c_video_client; + struct i2c_client *i2c_tveeprom_client; + + /* Frequency table */ + unsigned int freqTable[FREQTABLE_SIZE]; + + /* Stuff for handling low level control interaction with device */ + struct semaphore ctl_lock_sem; + 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 + + // 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; + + /* Tuner / frequency control stuff */ + unsigned int tuner_type; + unsigned long video_standards; + + /* saa7115 info: 0=unknown 1=new decoder 2=old decoder */ + int decoder_info; + + 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; + + enum pvr2_config config; + + /* Information about what audio signal we're hearing */ + int flag_stereo; + int flag_bilingual; + + /* 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/v4l_experimental/pvrusb2/pvrusb2-hdw.c b/v4l_experimental/pvrusb2/pvrusb2-hdw.c new file mode 100644 index 000000000..6853db776 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-hdw.c @@ -0,0 +1,2293 @@ +/* + * + * $Id: pvrusb2-hdw.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "compat.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <asm/semaphore.h> +#include "pvrusb2.h" +#include "pvrusb2-util.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-i2c.h" +#include "pvrusb2-tuner.h" +#include "pvrusb2-eeprom.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-audio.h" +#include "pvrusb2-video.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 hfull = 1; +static int width = 720; +static int initusbreset = 1; +static int procreload = 0; + +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(hfull, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(hfull, "full height video ?"); +module_param(width, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(width, "video width : 720,480,352"); + +#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 + +/* Various files we will load with firmware_entry */ +#define FIRMWARE1_FILE "pvrusb2.f1" +#define FIRMWARE2_FILE "pvrusb2.f2" + +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", +}; + + +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 "<unknown>"; +} + + +struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *hdw) +{ + return hdw->usb_dev; +} + + +unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *hdw) +{ + return hdw->serial_number; +} + + +struct pvr2_hdw *pvr2_hdw_find(int unit_number) +{ + if (unit_number < 0) return 0; + if (unit_number >= PVR_NUM) return 0; + return unit_pointers[unit_number]; +} + + +int pvr2_hdw_get_unit_number(struct pvr2_hdw *hdw) +{ + return hdw->unit_number; +} + + +/* + * 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; + const char *firmware_file = FIRMWARE1_FILE; + + trace_firmware("pvr2_upload_firmware1"); + + 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); + ret = request_firmware(&fw_entry, firmware_file, &hdw->usb_dev->dev); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware failed for '%s'", firmware_file); + return ret; + } + + 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 */ + return (ret == 8192) ? 0 : -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; + + trace_firmware("pvr2_upload_firmware2"); + + /* 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); + return ret; + } + + /* Now send firmware */ + + ret = request_firmware(&fw_entry, FIRMWARE2_FILE, &hdw->usb_dev->dev); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware failed for '%s'", FIRMWARE2_FILE); + return ret; + } + + fw_len = fw_entry->size; + + if (fw_len % FIRMWARE_CHUNK_SIZE) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "size of %s must be a multiple of 8192B", + FIRMWARE2_FILE); + 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 ", + FIRMWARE2_FILE,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----*/" + " pvr2_decoder_enable_output(0)"); + pvr2_decoder_enable_output(hdw,0); + } + 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_TUNER_CFG_STD) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_tuner_set_standard"); + pvr2_tuner_set_standard(hdw); + } + if (vmsk & PVR2_SUBSYS_TUNER_CFG_FREQ) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_tuner_set_freq"); + pvr2_tuner_set_freq(hdw); + } + if (vmsk & PVR2_SUBSYS_AUDIO_CFG_STD) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_audio_set_standard"); + pvr2_audio_set_standard(hdw); + } + if (vmsk & PVR2_SUBSYS_AUDIO_CFG_MODE) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_audio_set_stereo"); + pvr2_audio_set_stereo(hdw); + } + if (vmsk & PVR2_SUBSYS_AUDIO_CFG_VBBTM) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_audio_setvolume"); + pvr2_audio_setvolume(hdw); + } + if (vmsk & PVR2_SUBSYS_DIGITIZER_CFG_NORM) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_decoder_set_norm"); + pvr2_decoder_set_norm(hdw); + } + if (vmsk & PVR2_SUBSYS_DIGITIZER_CFG_INPUT) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_decoder_set_input"); + pvr2_decoder_set_input(hdw); + } + if (vmsk & PVR2_SUBSYS_DIGITIZER_CFG_SIZE) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_decoder_set_size"); + pvr2_decoder_set_size(hdw); + } + if (vmsk & PVR2_SUBSYS_DIGITIZER_CFG_AUDIO) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_decoder_set_audio"); + pvr2_decoder_set_audio(hdw); + } + if (vmsk & PVR2_SUBSYS_DIGITIZER_CFG_BCSH) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_decoder_set_bcsh"); + pvr2_decoder_set_bcsh(hdw); + } + 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----*/" + " pvr2_decoder_enable_output(1)"); + pvr2_decoder_enable_output(hdw,!0); + } + 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 void pvr2_hdw_setup_low(struct pvr2_hdw *hdw) +{ + 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; + } + + if (initusbreset) { + pvr2_hdw_device_reset(hdw); + } + if (!pvr2_hdw_dev_ok(hdw)) return; + + pvr2_i2c_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; + + pvr2_eeprom_analyze(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + + if (!pvr2_tuner_get_default_type(hdw)) { + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup: Tuner type overridden to %d", + hdw->tuner_type); + } + + pvr2_tuner_set_type(hdw); + 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) { + pvr2_stream_setup(hdw->vid_stream,hdw->usb_dev, + PVR2_VID_ENDPOINT); + } + + 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."); + } else { + pvr2_trace( + PVR2_TRACE_INFO, + "Device firmware (re)load executed;" + " it should now reset and reconnect."); + } + 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."); + 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); + 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->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); + + init_MUTEX(&hdw->ctl_lock_sem); + init_MUTEX(&hdw->big_lock_sem); + + + 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; + } + pvr2_i2c_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; + } + 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 this. 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 = "<out of range>"; + } + } else { + ctl_value = "<integer>"; + } + 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) { + if (!hdw->controls[PVR2_CID_VRES].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: + 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].dirty) { + 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_FREQUENCY].dirty = !0; + stale_subsys_mask |= (PVR2_SUBSYS_DIGITIZER_CFG_NORM | + PVR2_SUBSYS_TUNER_CFG_STD | + PVR2_SUBSYS_AUDIO_CFG_STD); + } + + if (hdw->controls[PVR2_CID_HRES].dirty || + hdw->controls[PVR2_CID_VRES].dirty) { + stale_subsys_mask |= PVR2_SUBSYS_DIGITIZER_CFG_SIZE; + } + + if (hdw->controls[PVR2_CID_BRIGHTNESS].dirty || + hdw->controls[PVR2_CID_CONTRAST].dirty || + hdw->controls[PVR2_CID_SATURATION].dirty || + hdw->controls[PVR2_CID_HUE].dirty) { + stale_subsys_mask |= PVR2_SUBSYS_DIGITIZER_CFG_BCSH; + } + + if (hdw->controls[PVR2_CID_SRATE].dirty) { + stale_subsys_mask |= PVR2_SUBSYS_DIGITIZER_CFG_AUDIO; + } + + 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) { + stale_subsys_mask |= PVR2_SUBSYS_ENC_CFG; + } + + if (hdw->controls[PVR2_CID_AUDIOMODE].dirty || + hdw->controls[PVR2_CID_INPUT].dirty) { + stale_subsys_mask |= PVR2_SUBSYS_AUDIO_CFG_MODE; + } + + if (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) { + stale_subsys_mask |= PVR2_SUBSYS_AUDIO_CFG_VBBTM; + } + + if (hdw->controls[PVR2_CID_INPUT].dirty) { + stale_subsys_mask |= PVR2_SUBSYS_DIGITIZER_CFG_INPUT; + } + + if (hdw->controls[PVR2_CID_FREQUENCY].dirty) { + stale_subsys_mask |= PVR2_SUBSYS_TUNER_CFG_FREQ; + } + + if (stale_subsys_mask & (PVR2_SUBSYS_DIGITIZER_CFG_NORM | + PVR2_SUBSYS_TUNER_CFG_STD | + PVR2_SUBSYS_AUDIO_CFG_STD | + PVR2_SUBSYS_DIGITIZER_CFG_SIZE | + PVR2_SUBSYS_DIGITIZER_CFG_AUDIO | + PVR2_SUBSYS_ENC_CFG)) { + stale_subsys_mask |= hdw->subsys_stream_mask; + } + + for (idx = 0; idx < PVR2_CID_COUNT; idx++) { + hdw->controls[idx].dirty = 0; + } + + 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; +} + + +/* 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 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 (pvr2_decoder_is_tuned(hdw)) { + msk |= PVR2_SIGNAL_OK; + if (pvr2_audio_update_status(hdw) == 0) { + 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_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; + 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); + } + 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); + } +} + + +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); +} + + +/* + 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/v4l_experimental/pvrusb2/pvrusb2-hdw.h b/v4l_experimental/pvrusb2/pvrusb2-hdw.h new file mode 100644 index 000000000..55feeaac2 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-hdw.h @@ -0,0 +1,415 @@ +/* + * + * $Id: pvrusb2-hdw.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_HDW_H +#define __PVRUSB2_HDW_H + +#include <linux/usb.h> +#include "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_SECAM_L 1 +#define PVR2_CVAL_VIDEOSTANDARD_PAL_BG 2 +#define PVR2_CVAL_VIDEOSTANDARD_PAL_I 3 +#define PVR2_CVAL_VIDEOSTANDARD_PAL_DK 4 +#define PVR2_CVAL_VIDEOSTANDARD_MIN PVR2_CVAL_VIDEOSTANDARD_NTSC_M +#define PVR2_CVAL_VIDEOSTANDARD_MAX PVR2_CVAL_VIDEOSTANDARD_PAL_DK + +/* 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 0x00000001 +#define PVR2_SUBSYS_TUNER_CFG_STD 0x00000002 +#define PVR2_SUBSYS_TUNER_CFG_FREQ 0x00000004 +#define PVR2_SUBSYS_AUDIO_CFG_VBBTM 0x00000008 +#define PVR2_SUBSYS_AUDIO_CFG_STD 0x00000010 +#define PVR2_SUBSYS_AUDIO_CFG_MODE 0x00000020 +#define PVR2_SUBSYS_DIGITIZER_CFG_NORM 0x00000040 +#define PVR2_SUBSYS_DIGITIZER_CFG_INPUT 0x00000080 +#define PVR2_SUBSYS_DIGITIZER_CFG_SIZE 0x00000100 +#define PVR2_SUBSYS_DIGITIZER_CFG_AUDIO 0x00000200 +#define PVR2_SUBSYS_DIGITIZER_CFG_BCSH 0x00000400 +#define PVR2_SUBSYS_ENC_CFG 0x00000800 +#define PVR2_SUBSYS_DIGITIZER_RUN 0x00001000 +#define PVR2_SUBSYS_USBSTREAM_RUN 0x00002000 +#define PVR2_SUBSYS_ENC_RUN 0x00004000 + +#define PVR2_SUBSYS_TUNER_CFG_ALL ( \ + PVR2_SUBSYS_TUNER_CFG_STD | \ + PVR2_SUBSYS_TUNER_CFG_FREQ ) +#define PVR2_SUBSYS_AUDIO_CFG_ALL ( \ + PVR2_SUBSYS_AUDIO_CFG_MODE | \ + PVR2_SUBSYS_AUDIO_CFG_STD | \ + PVR2_SUBSYS_AUDIO_CFG_VBBTM ) +#define PVR2_SUBSYS_DIGITIZER_CFG_ALL ( \ + PVR2_SUBSYS_DIGITIZER_CFG_NORM | \ + PVR2_SUBSYS_DIGITIZER_CFG_INPUT | \ + PVR2_SUBSYS_DIGITIZER_CFG_SIZE | \ + PVR2_SUBSYS_DIGITIZER_CFG_AUDIO | \ + PVR2_SUBSYS_DIGITIZER_CFG_BCSH ) +#define PVR2_SUBSYS_CFG_ALL ( \ + PVR2_SUBSYS_ENC_FIRMWARE | \ + PVR2_SUBSYS_TUNER_CFG_ALL | \ + PVR2_SUBSYS_AUDIO_CFG_ALL | \ + PVR2_SUBSYS_DIGITIZER_CFG_ALL | \ + 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); + +/* 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 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); + +/* 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 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/v4l_experimental/pvrusb2/pvrusb2-i2c.c b/v4l_experimental/pvrusb2/pvrusb2-i2c.c new file mode 100644 index 000000000..d5cb7467a --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-i2c.c @@ -0,0 +1,441 @@ +/* + * + * $Id: pvrusb2-i2c.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "compat.h" +#include "pvrusb2-i2c.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-tuner.h" +#include "pvrusb2-debug.h" + +#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__) + +#ifndef I2C_DRIVERID_SAA7115 +// Using temporary hack for missing I2C driver-ID for saa7115 +#define I2C_DRIVERID_SAA7115 I2C_DRIVERID_EXP1 +#endif + +#ifndef I2C_DRIVERID_TVEEPROM +// Using temporary hack for missing I2C driver-ID for tveeprom +#define I2C_DRIVERID_TVEEPROM I2C_DRIVERID_EXP2 +#endif + +/* + + 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 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: + 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; +} + +static int pvr2_i2c_attach_inform(struct i2c_client *client) +{ + struct pvr2_hdw *hdw = (struct pvr2_hdw *)(client->adapter->algo_data); + int id; + id = client->driver->id; + trace_i2c("i2c_attach [client=%s @ 0x%x id=0x%x]", + client->name, + client->addr,id); + if (!(hdw->i2c_audio_client) && (id == I2C_DRIVERID_MSP3400)) { + trace_i2c("attaching msp3400 I2C client"); + hdw->i2c_audio_client = client; + } + if (!(hdw->i2c_tuner_client) && (id == I2C_DRIVERID_TUNER)) { + trace_i2c("attaching tuner I2C client"); + hdw->i2c_tuner_client = client; + pvr2_tuner_set_type(hdw); + } + if (!(hdw->i2c_video_client) && (id == I2C_DRIVERID_SAA7115)) { + trace_i2c("attaching saa7115 I2C client"); + hdw->i2c_video_client = client; + } + if (!(hdw->i2c_tveeprom_client) && (id == I2C_DRIVERID_TVEEPROM)) { + trace_i2c("attaching tveeprom I2C client"); + hdw->i2c_tveeprom_client = client; + } + + return 0; +} + +int pvr2_i2c_cmd(struct i2c_client *cp,unsigned int cmd,void *arg) +{ + int stat; + if (!cp) return -EINVAL; + if (!(cp->driver)) return -EINVAL; + if (!(cp->driver->command)) return -EINVAL; + if (!try_module_get(cp->driver->owner)) return -EAGAIN; + stat = cp->driver->command(cp,cmd,arg); + module_put(cp->driver->owner); + return stat; +} + +int pvr2_i2c_tuner_cmd(struct pvr2_hdw *hdw,unsigned int cmd,void *arg) +{ + int stat = pvr2_i2c_cmd(hdw->i2c_tuner_client,cmd,arg); + if (stat < 0) trace_i2c("pvr2_i2c_tuner_cmd failed with status %d", + stat); + return stat; +} + +int pvr2_i2c_msp3400_cmd(struct pvr2_hdw *hdw,unsigned int cmd,void *arg) +{ + int stat = pvr2_i2c_cmd(hdw->i2c_audio_client,cmd,arg); + if (stat < 0) trace_i2c("pvr2_i2c_msp3400_cmd failed with status %d", + stat); + return stat; +} + +int pvr2_i2c_saa7115_cmd(struct pvr2_hdw *hdw,unsigned int cmd,void *arg) +{ + int stat = pvr2_i2c_cmd(hdw->i2c_video_client,cmd,arg); + if (stat < 0) trace_i2c("pvr2_i2c_saa7115_cmd failed with status %d", + stat); + return stat; +} + +int pvr2_i2c_tveeprom_cmd(struct pvr2_hdw *hdw,unsigned int cmd,void *arg) +{ + int stat = pvr2_i2c_cmd(hdw->i2c_tveeprom_client,cmd,arg); + if (stat < 0) { + trace_i2c("pvr2_i2c_tveeprom_cmd failed with status %d",stat); + } + return stat; +} + +static int pvr2_i2c_detach_inform(struct i2c_client *client) +{ + struct pvr2_hdw *hdw = (struct pvr2_hdw *)(client->adapter->algo_data); + trace_i2c("pvr2_i2c_detach [client=%s @ 0x%x]", + client->name, + client->addr); + if (hdw->i2c_audio_client == client) { + trace_i2c("detaching msp3400 I2C client"); + hdw->i2c_audio_client = 0; + } + if (hdw->i2c_tuner_client == client) { + trace_i2c("detaching tuner I2C client"); + hdw->i2c_tuner_client = 0; + } + if (hdw->i2c_video_client == client) { + trace_i2c("detaching saa7115 I2C client"); + hdw->i2c_video_client = 0; + } + if (hdw->i2c_tveeprom_client == client) { + trace_i2c("detaching tveeprom I2C client"); + hdw->i2c_tveeprom_client = 0; + } + + return 0; +} + +static struct i2c_algorithm pvr2_i2c_algo_template = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + .id = I2C_ALGO_BIT | 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, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + .id = I2C_ALGO_BIT | I2C_HW_B_BT848, +#else + .id = I2C_HW_B_BT848, +#endif + .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; + for (i = 0; i < 128; i++) { + msg[0].addr = i; + rc = i2c_transfer(&hdw->i2c_adap,msg, + sizeof(msg)/sizeof(msg[0])); + if (rc < 0) continue; + printk("%s: i2c scan: found device @ 0x%x\n",hdw->name,i); + } +} + +void pvr2_i2c_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; + i2c_add_adapter(&hdw->i2c_adap); + hdw->i2c_linked = !0; + if (i2c_scan) do_i2c_scan(hdw); +} + +void pvr2_i2c_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/v4l_experimental/pvrusb2/pvrusb2-i2c.h b/v4l_experimental/pvrusb2/pvrusb2-i2c.h new file mode 100644 index 000000000..5379990b9 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-i2c.h @@ -0,0 +1,44 @@ +/* + * + * $Id: pvrusb2-i2c.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_I2C_H +#define __PVRUSB2_I2C_H + +struct pvr2_hdw; + +void pvr2_i2c_init(struct pvr2_hdw *); +void pvr2_i2c_done(struct pvr2_hdw *); +int pvr2_i2c_tuner_cmd(struct pvr2_hdw *,unsigned int,void *); +int pvr2_i2c_msp3400_cmd(struct pvr2_hdw *,unsigned int,void *); +int pvr2_i2c_saa7115_cmd(struct pvr2_hdw *,unsigned int,void *); +int pvr2_i2c_tveeprom_cmd(struct pvr2_hdw *,unsigned int,void *); + +#endif /* __PVRUSB2_I2C_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/v4l_experimental/pvrusb2/pvrusb2-io.c b/v4l_experimental/pvrusb2/pvrusb2-io.c new file mode 100644 index 000000000..935978a14 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-io.c @@ -0,0 +1,682 @@ +/* + * + * $Id: pvrusb2-io.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-io.h" +#include "pvrusb2-debug.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/semaphore.h> + +#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; + struct semaphore sem; +}; + +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); + init_MUTEX(&sp->sem); + 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) +{ + down(&sp->sem); do { + pvr2_stream_internal_flush(sp); + pvr2_stream_buffer_count(sp,0); + } while (0); up(&sp->sem); +} + +static void buffer_complete(struct urb *urb, struct pt_regs *regs) +{ + struct pvr2_buffer *bp = urb->context; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + bp->used_count = 0; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferComplete %p stat=%d cnt=%d", + bp,urb->status,urb->actual_length); + if ((!(urb->status)) || + (urb->status == -ENOENT) || + (urb->status == -ECONNRESET) || + (urb->status == -ESHUTDOWN)) { + bp->used_count = urb->actual_length; + } + bp->status = urb->status; + 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) +{ + down(&sp->sem); do { + pvr2_stream_internal_flush(sp); + sp->dev = dev; + sp->endpoint = endpoint; + } while(0); up(&sp->sem); +} + +void pvr2_stream_set_callback(struct pvr2_stream *sp, + pvr2_stream_callback func, + void *data) +{ + unsigned long irq_flags; + down(&sp->sem); 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); up(&sp->sem); +} + +/* 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; + down(&sp->sem); do { + sp->buffer_target_count = cnt; + ret = pvr2_stream_achieve_buffer_count(sp); + } while(0); up(&sp->sem); + 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) +{ + down(&sp->sem); do { + pvr2_stream_internal_flush(sp); + } while(0); up(&sp->sem); +} + +void pvr2_stream_kill(struct pvr2_stream *sp) +{ + struct pvr2_buffer *bp; + down(&sp->sem); 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); up(&sp->sem); +} + +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; + down(&sp->sem); 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); up(&sp->sem); + return ret; +} + +int pvr2_buffer_idle(struct pvr2_buffer *bp) +{ + struct pvr2_stream *sp; + if (!bp) return -EINVAL; + sp = bp->stream; + down(&sp->sem); 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); up(&sp->sem); + 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; + down(&sp->sem); 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); up(&sp->sem); + 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/v4l_experimental/pvrusb2/pvrusb2-io.h b/v4l_experimental/pvrusb2/pvrusb2-io.h new file mode 100644 index 000000000..17e01345b --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-io.h @@ -0,0 +1,101 @@ +/* + * + * $Id: pvrusb2-io.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_IO_H +#define __PVRUSB2_IO_H + +#include <linux/usb.h> +#include <linux/list.h> + +typedef void (*pvr2_stream_callback)(void *); + +enum pvr2_buffer_state { + pvr2_buffer_state_none = 0, // Not on any list + pvr2_buffer_state_idle = 1, // Buffer is ready to be used again + pvr2_buffer_state_queued = 2, // Buffer has been queued for filling + pvr2_buffer_state_ready = 3, // Buffer has data available +}; + +struct pvr2_stream; +struct pvr2_buffer; + +const char *pvr2_buffer_state_decode(enum pvr2_buffer_state); + +/* Initialize / tear down stream structure */ +struct pvr2_stream *pvr2_stream_create(void); +void pvr2_stream_destroy(struct pvr2_stream *); +void pvr2_stream_setup(struct pvr2_stream *, + struct usb_device *dev,int endpoint); +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/v4l_experimental/pvrusb2/pvrusb2-ioread.c b/v4l_experimental/pvrusb2/pvrusb2-ioread.c new file mode 100644 index 000000000..5517e545c --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-ioread.c @@ -0,0 +1,357 @@ +/* + * + * $Id: pvrusb2-ioread.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-ioread.h" +#include "pvrusb2-debug.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/semaphore.h> +#include <asm/uaccess.h> + +#define BUFFER_COUNT 32 +#define BUFFER_SIZE PAGE_ALIGN(0x4000) + +struct pvr2_ioread { + struct pvr2_stream *stream; + char *buffer_storage[BUFFER_COUNT]; + int resid_offs; + int enabled; + int stream_running; + int spigot_open; + struct semaphore sem; +}; + +static int pvr2_ioread_init(struct pvr2_ioread *cp) +{ + unsigned int idx; + + cp->stream = 0; + init_MUTEX(&cp->sem); + + 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; + + down(&cp->sem); 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); up(&cp->sem); + + return 0; +} + +int pvr2_ioread_set_enabled(struct pvr2_ioread *cp,int fl) +{ + int ret = 0; + if ((!fl) == (!(cp->enabled))) return ret; + + down(&cp->sem); do { + if (fl) { + ret = pvr2_ioread_start(cp); + } else { + pvr2_ioread_stop(cp); + } + } while (0); up(&cp->sem); + 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; + + down(&cp->sem); 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); up(&cp->sem); + + 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/v4l_experimental/pvrusb2/pvrusb2-ioread.h b/v4l_experimental/pvrusb2/pvrusb2-ioread.h new file mode 100644 index 000000000..ec2a23325 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-ioread.h @@ -0,0 +1,47 @@ +/* + * + * $Id: pvrusb2-ioread.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_IOREAD_H +#define __PVRUSB2_IOREAD_H + +#include "pvrusb2-io.h" + +struct pvr2_ioread; + +struct pvr2_ioread *pvr2_ioread_create(void); +void pvr2_ioread_destroy(struct pvr2_ioread *); +int pvr2_ioread_setup(struct pvr2_ioread *,struct pvr2_stream *); +struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *); +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/v4l_experimental/pvrusb2/pvrusb2-main.c b/v4l_experimental/pvrusb2/pvrusb2-main.c new file mode 100644 index 000000000..4ae24f6b8 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-main.c @@ -0,0 +1,177 @@ +/* + * + * $Id: pvrusb2-main.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "compat.h" +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/smp_lock.h> +#include <linux/usb.h> +#include <linux/videodev.h> + +#include "pvrusb2-version.h" +#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 <isely@pobox.com>" +#define DRIVER_DESC "Hauppauge WinTV-PVR-USB2 MPEG2 Encoder/Tuner" + +#define DEFAULT_DEBUG_MASK (PVR2_TRACE_ERROR_LEGS| \ + PVR2_TRACE_INFO| \ + PVR2_TRACE_TRAP| \ + PVR2_TRACE_FIRMWARE| \ + PVR2_TRACE_EEPROM | \ + PVR2_TRACE_INIT | \ + PVR2_TRACE_I2C | \ + PVR2_TRACE_START_STOP | \ + PVR2_TRACE_CTL | \ + PVR2_TRACE_DEBUGIFC | \ + 0) + +int debug = DEFAULT_DEBUG_MASK; + +module_param(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 = { + owner: THIS_MODULE, + 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"); + + class_ptr = pvr2_sysfs_class_create(); + + ret = usb_register(&pvr_driver); + + if (ret == 0) + info(DRIVER_DESC " : " DRIVER_VERSION); + if (debug) info("Debug mask is %d (0x%x)",debug,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/v4l_experimental/pvrusb2/pvrusb2-sysfs.c b/v4l_experimental/pvrusb2/pvrusb2-sysfs.c new file mode 100644 index 000000000..42e012d96 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-sysfs.c @@ -0,0 +1,800 @@ +/* + * + * $Id: pvrusb2-sysfs.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/semaphore.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 <isely@pobox.com> 30-April-2005 + + This next batch of horrible preprocessor hackery is needed because the + kernel's class_device_attribute mechanism fails to pass the actual + attribute through to the show / store functions, which means we have no + way to package up any attribute-specific parameters, like for example the + control id. So we work around this brain-damage by encoding the control + id into the show / store functions themselves and pick the function based + on the control id we're setting up. These macros try to ease the pain. + Yuck. +*/ + +#define CREATE_SHOW_INSTANCE(sf_name,ctl_id) \ +static ssize_t sf_name##_##ctl_id(struct class_device *class_dev,char *buf) \ +{ return sf_name(ctl_id,class_dev,buf); } + +#define CREATE_STORE_INSTANCE(sf_name,ctl_id) \ +static ssize_t sf_name##_##ctl_id(struct class_device *class_dev,const char *buf,size_t count) \ +{ return sf_name(ctl_id,class_dev,buf,count); } + +#define CREATE_BATCH(ctl_id) \ +CREATE_SHOW_INSTANCE(show_name,ctl_id) \ +CREATE_SHOW_INSTANCE(show_min,ctl_id) \ +CREATE_SHOW_INSTANCE(show_max,ctl_id) \ +CREATE_SHOW_INSTANCE(show_val_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; + clp->class.hotplug = pvr2_sysfs_hotplug; + if (class_register(&clp->class)) { + pvr2_sysfs_trace( + "Registration failed for pvr2_sysfs_class id=%p",clp); + kfree(clp); + clp = 0; + } + return clp; +} + + +void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *clp) +{ + class_unregister(&clp->class); +} + + +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; + 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/v4l_experimental/pvrusb2/pvrusb2-sysfs.h b/v4l_experimental/pvrusb2/pvrusb2-sysfs.h new file mode 100644 index 000000000..20dcc3256 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-sysfs.h @@ -0,0 +1,47 @@ +/* + * + * $Id: pvrusb2-sysfs.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_SYSFS_H +#define __PVRUSB2_SYSFS_H + +#include <linux/list.h> +#include <linux/sysfs.h> +#include "pvrusb2-context.h" + +struct pvr2_sysfs; +struct pvr2_sysfs_class; + +struct pvr2_sysfs_class *pvr2_sysfs_class_create(void); +void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *); + +struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *, + struct pvr2_sysfs_class *); + +#endif /* __PVRUSB2_SYSFS_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/v4l_experimental/pvrusb2/pvrusb2-tuner.c b/v4l_experimental/pvrusb2/pvrusb2-tuner.c new file mode 100644 index 000000000..ffbc66294 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-tuner.c @@ -0,0 +1,154 @@ +/* + * + * $Id: pvrusb2-tuner.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2.h" +#include "pvrusb2-util.h" +#include "pvrusb2-tuner.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-i2c.h" +#include "pvrusb2-debug.h" +#include "compat.h" +#include <linux/videodev.h> +#include <media/tuner.h> + +#define PVR_STD_I2C_ADDR 0x43 + +static int tuner[PVR_NUM] = { [0 ... PVR_NUM-1] = -1 }; + +module_param_array(tuner, int, NULL, 0444); +MODULE_PARM_DESC(tuner,"specify installed tuner type"); + +static u32 pvr_tbl_standard[] = { + [PVR2_CVAL_VIDEOSTANDARD_PAL_BG] = 0x00d47009, /* PVR_STD_PAL_BG */ + [PVR2_CVAL_VIDEOSTANDARD_PAL_I] = 0x00d4700a, /* PVR_STD_PAL_I */ + [PVR2_CVAL_VIDEOSTANDARD_PAL_DK] = 0x00d4700b, /* PVR_STD_PAL_DK */ + [PVR2_CVAL_VIDEOSTANDARD_SECAM_L] = 0x00c4100b, /* PVR_STD_SECAM */ + [PVR2_CVAL_VIDEOSTANDARD_NTSC_M] = 0x00163044 /* PVR_STD_NTSC */ +}; + +#define trace_tuner(...) pvr2_trace(PVR2_TRACE_TUNER,__VA_ARGS__) + +int pvr2_tuner_get_default_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; +} + +int pvr2_tuner_set_freq(struct pvr2_hdw *hdw) +{ + int stat; + struct v4l2_frequency freq; + freq.frequency = hdw->controls[PVR2_CID_FREQUENCY].value / 62500; + freq.tuner = 0; + freq.type = V4L2_TUNER_ANALOG_TV; + trace_tuner("pvr2_tuner_set_freq(%d)",freq.frequency); + stat = pvr2_i2c_tuner_cmd(hdw,VIDIOC_S_FREQUENCY,&freq); + if (stat < 0) return stat; + /* This kicks the audio controller... */ + stat = pvr2_i2c_msp3400_cmd(hdw,VIDIOCSFREQ,0); + if (stat < 0) return stat; + hdw->subsys_enabled_mask |= PVR2_SUBSYS_TUNER_CFG_FREQ; + return 0; +} + +int pvr2_tuner_set_type(struct pvr2_hdw *hdw) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13) + struct tuner_setup setup; +#endif + trace_tuner("pvr2_tuner_set_type(%d)",hdw->tuner_type); + if (((int)(hdw->tuner_type)) < 0) { + trace_tuner("tuner type not set yet"); + return 0; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13) + 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 */ + return pvr2_i2c_tuner_cmd(hdw,TUNER_SET_TYPE_ADDR,&setup); +#else + return pvr2_i2c_tuner_cmd(hdw,TUNER_SET_TYPE,&hdw->tuner_type); +#endif +} + +int pvr2_tuner_set_standard(struct pvr2_hdw *hdw) +{ + struct i2c_msg msg[1]; + u8 buff [4]; + v4l2_std_id vs; + int cvstd = hdw->controls[PVR2_CID_VIDEOSTANDARD].value; + + trace_tuner("pvr2_tuner_set_standard(%d)", + hdw->controls[PVR2_CID_VIDEOSTANDARD].value); + + /* FIXME: Might be tuner dependant ? Need more hardware info... */ + /* MCI 15-May-2005: I have absolutely no idea what this is doing + or who it is talking to. */ + PVR2_DECOMPOSE_BE( + buff, 0,pvr_tbl_standard[cvstd]); + msg[0].addr = PVR_STD_I2C_ADDR; + msg[0].flags = 0; + msg[0].len = sizeof(buff); + msg[0].buf = buff; + i2c_transfer(&hdw->i2c_adap,msg,sizeof(msg)/sizeof(msg[0])); + + /* Also tell tuner.ko about the video standard. */ + switch (cvstd) { + default: + case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: + vs = V4L2_STD_NTSC; + 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; + } + pvr2_i2c_tuner_cmd(hdw,VIDIOC_S_STD,&vs); + + hdw->subsys_enabled_mask |= PVR2_SUBSYS_TUNER_CFG_STD; + 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/v4l_experimental/pvrusb2/pvrusb2-tuner.h b/v4l_experimental/pvrusb2/pvrusb2-tuner.h new file mode 100644 index 000000000..501f2af48 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-tuner.h @@ -0,0 +1,41 @@ +/* + * + * $Id: pvrusb2-tuner.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_TUNER_H +#define __PVRUSB2_TUNER_H + +struct pvr2_hdw; + +int pvr2_tuner_get_default_type(struct pvr2_hdw *); +int pvr2_tuner_set_freq(struct pvr2_hdw *); +int pvr2_tuner_set_type(struct pvr2_hdw *); +int pvr2_tuner_set_standard(struct pvr2_hdw *); + +#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/v4l_experimental/pvrusb2/pvrusb2-util.h b/v4l_experimental/pvrusb2/pvrusb2-util.h new file mode 100644 index 000000000..e8c2cc940 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-util.h @@ -0,0 +1,63 @@ +/* + * + * $Id: pvrusb2-util.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_UTIL_H +#define __PVRUSB2_UTIL_H + +#define PVR2_DECOMPOSE_LE(t,i,d) \ + do { \ + (t)[i] = (d) & 0xff;\ + (t)[i+1] = ((d) >> 8) & 0xff;\ + (t)[i+2] = ((d) >> 16) & 0xff;\ + (t)[i+3] = ((d) >> 24) & 0xff;\ + } while(0) + +#define PVR2_DECOMPOSE_BE(t,i,d) \ + do { \ + (t)[i+3] = (d) & 0xff;\ + (t)[i+2] = ((d) >> 8) & 0xff;\ + (t)[i+1] = ((d) >> 16) & 0xff;\ + (t)[i] = ((d) >> 24) & 0xff;\ + } while(0) + +#define PVR2_COMPOSE_LE(t,i) \ + ((((u32)((t)[i+3])) << 24) | \ + (((u32)((t)[i+2])) << 16) | \ + (((u32)((t)[i+1])) << 8) | \ + ((u32)((t)[i]))) + +#define PVR2_COMPOSE_BE(t,i) \ + ((((u32)((t)[i])) << 24) | \ + (((u32)((t)[i+1])) << 16) | \ + (((u32)((t)[i+2])) << 8) | \ + ((u32)((t)[i+3]))) + + +#endif /* __PVRUSB2_UTIL_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/v4l_experimental/pvrusb2/pvrusb2-v4l2.c b/v4l_experimental/pvrusb2/pvrusb2-v4l2.c new file mode 100644 index 000000000..c10c1a4e6 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-v4l2.c @@ -0,0 +1,1302 @@ +/* + * + * $Id: pvrusb2-v4l2.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "compat.h" +#include <linux/kernel.h> +#include <linux/videodev.h> + +#include "pvrusb2-context.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-v4l2.h" +#include "pvrusb2-ioread.h" +#include <linux/videodev.h> + + +#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 semaphore sem; +}; + +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, + .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 + +static const char *get_cmd_code(int cmd) +{ + switch (cmd) { + case VIDIOC_QUERYCAP: + return "VIDIOC_QUERYCAP"; + case VIDIOC_G_PRIORITY: + return "VIDIOC_G_PRIORITY"; + case VIDIOC_S_PRIORITY: + return "VIDIOC_S_PRIORITY"; + case VIDIOC_ENUMSTD: + return "VIDIOC_ENUMSTD"; + case VIDIOC_G_STD: + return "VIDIOC_G_STD"; + case VIDIOC_S_STD: + return "VIDIOC_S_STD"; + case VIDIOC_ENUMINPUT: + return "VIDIOC_ENUMINPUT"; + case VIDIOC_G_INPUT: + return "VIDIOC_G_INPUT"; + case VIDIOC_S_INPUT: + return "VIDIOC_S_INPUT"; + case VIDIOC_ENUMAUDIO: + return "VIDIOC_ENUMAUDIO"; + case VIDIOC_G_AUDIO: + return "VIDIOC_G_AUDIO"; + case VIDIOC_S_AUDIO: + return "VIDIOC_S_AUDIO"; + case VIDIOC_G_TUNER: + return "VIDIOC_G_TUNER"; + case VIDIOC_S_TUNER: + return "VIDIOC_S_TUNER"; + case VIDIOC_S_FREQUENCY: + return "VIDIOC_S_FREQUENCY"; + case VIDIOC_G_FREQUENCY: + return "VIDIOC_G_FREQUENCY"; + case VIDIOC_ENUM_FMT: + return "VIDIOC_ENUM_FMT"; + case VIDIOC_G_FMT: + return "VIDIOC_G_FMT"; + case VIDIOC_TRY_FMT: + return "VIDIOC_TRY_FMT"; + case VIDIOC_S_FMT: + return "VIDIOC_S_FMT"; + case VIDIOC_QBUF: + return "VIDIOC_QBUF"; + case VIDIOC_QUERYBUF: + return "VIDIOC_QUERYBUF"; + case VIDIOC_DQBUF: + return "VIDIOC_DQBUF"; + case VIDIOC_REQBUFS: + return "VIDIOC_REQBUFS"; + case VIDIOC_STREAMON: + return "VIDIOC_STREAMON"; + case VIDIOC_STREAMOFF: + return "VIDIOC_STREAMOFF"; + case VIDIOC_QUERYCTRL: + return "VIDIOC_QUERYCTRL"; + case VIDIOC_QUERYMENU: + return "VIDIOC_QUERYMENU"; + case VIDIOC_G_CTRL: + return "VIDIOC_G_CTRL"; + case VIDIOC_S_CTRL: + return "VIDIOC_S_CTRL"; + case VIDIOCGMBUF: + return "VIDIOCGMBUF"; + default : + break; + } + return "unknown"; +} + + +/* + * 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; + + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "pvr2_v4l2_do_ioctl cmd=%s (%d)",get_cmd_code(cmd),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; + 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){ + 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; + } + + 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 is_ntsc = (pvr2_hdw_get_ctl_value( + hdw, + PVR2_CID_VIDEOSTANDARD) == + PVR2_CVAL_VIDEOSTANDARD_NTSC_M); + int hf = is_ntsc ? 480 : 576; + int hh = (int) (hf / 2); + int h = vf->fmt.pix.height; + int w = vf->fmt.pix.width; + + 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; + } + + default : + ret = v4l_compat_translate_ioctl(inode,file,cmd, + arg,pvr2_v4l2_do_ioctl); + } + + pvr2_hdw_commit_ctl(hdw); + + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "pvr2_v4l2_do_ioctl failure, ret=%d (0x%x)", + ret,ret); + } 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_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_hdw_v4l_store_minor_number(fhp->channel.mc_head->hdw,-1); + + 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_MUTEX(&fhp->sem); + 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; + copy_to_user(buff,tbuf,c2); + 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/v4l_experimental/pvrusb2/pvrusb2-v4l2.h b/v4l_experimental/pvrusb2/pvrusb2-v4l2.h new file mode 100644 index 000000000..9dd446469 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-v4l2.h @@ -0,0 +1,40 @@ +/* + * + * $Id: pvrusb2-v4l2.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_V4L2_H +#define __PVRUSB2_V4L2_H + +#include "pvrusb2-context.h" + +struct pvr2_v4l2; + +struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *); + +#endif /* __PVRUSB2_V4L2_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/v4l_experimental/pvrusb2/pvrusb2-version.h b/v4l_experimental/pvrusb2/pvrusb2-version.h new file mode 100644 index 000000000..5e3c98a68 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-version.h @@ -0,0 +1,4 @@ +#ifndef _INCLUDED_PVRUSB2_DRIVER_VERSION +#define _INCLUDED_PVRUSB2_DRIVER_VERSION +#define DRIVER_VERSION "20051113 (from v4l tree)" +#endif diff --git a/v4l_experimental/pvrusb2/pvrusb2-video.c b/v4l_experimental/pvrusb2/pvrusb2-video.c new file mode 100644 index 000000000..7b4e973c5 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-video.c @@ -0,0 +1,216 @@ +/* + * + * $Id: pvrusb2-video.c,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + + This 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. + + 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-i2c.h" +#include "pvrusb2-video.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev.h> +#include <media/v4l2-common.h> + +#define pvr2_decoder_trace(...) pvr2_trace(PVR2_TRACE_DECODER,__VA_ARGS__) + +int pvr2_decoder_enable_output(struct pvr2_hdw *hdw,int fl) +{ + int status; + pvr2_decoder_trace("pvr2_decoder_enable_output(%d)",fl); + if (fl) { + status = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_STREAMON,0); + } else { + status = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_STREAMOFF,0); + } + if (!status) { + hdw->subsys_enabled_mask = + ((hdw->subsys_enabled_mask & + ~PVR2_SUBSYS_DIGITIZER_RUN) | + (fl ? PVR2_SUBSYS_DIGITIZER_RUN : 0)); + } + return status; +} + +int pvr2_decoder_set_input(struct pvr2_hdw *hdw) +{ + int status; + int v = 0; + pvr2_decoder_trace("pvr2_decoder_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 -EINVAL; + } + status = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_S_INPUT,&v); + if (!status) { + hdw->subsys_enabled_mask |= PVR2_SUBSYS_DIGITIZER_CFG_INPUT; + } + return status; +} + +int pvr2_decoder_set_bcsh(struct pvr2_hdw *hdw) +{ + struct v4l2_control ctrl; + int ret; + memset(&ctrl,0,sizeof(ctrl)); + + pvr2_decoder_trace("pvr2_decoder_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); + + ctrl.id = V4L2_CID_BRIGHTNESS; + ctrl.value = hdw->controls[PVR2_CID_BRIGHTNESS].value; + ret = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + if (ret) return ret; + ctrl.id = V4L2_CID_CONTRAST; + ctrl.value = hdw->controls[PVR2_CID_CONTRAST].value; + ret = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + if (ret) return ret; + ctrl.id = V4L2_CID_SATURATION; + ctrl.value = hdw->controls[PVR2_CID_SATURATION].value; + ret = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + if (ret) return ret; + ctrl.id = V4L2_CID_HUE; + ctrl.value = hdw->controls[PVR2_CID_HUE].value; + ret = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + if (ret) return ret; + + hdw->subsys_enabled_mask |= PVR2_SUBSYS_DIGITIZER_CFG_BCSH; + return 0; +} + +int pvr2_decoder_is_tuned(struct pvr2_hdw *hdw) +{ + struct v4l2_tuner vt; + int ret; + + memset(&vt,0,sizeof(vt)); + ret = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_G_TUNER,&vt); + if (ret < 0) return 0; + return vt.signal != 0; +} + +int pvr2_decoder_set_size(struct pvr2_hdw *hdw) +{ + struct v4l2_format fmt; + int ret; + + 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_decoder_trace("pvr2_decoder_set_size(%dx%d)", + fmt.fmt.pix.width,fmt.fmt.pix.height); + + ret = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_S_FMT,&fmt); + if (ret) return ret; + + hdw->subsys_enabled_mask |= PVR2_SUBSYS_DIGITIZER_CFG_SIZE; + return 0; +} + +int pvr2_decoder_set_audio(struct pvr2_hdw *hdw) +{ + int ret; + enum v4l2_audio_clock_freq val; + + pvr2_decoder_trace("pvr2_decoder_set_audio %d", + hdw->controls[PVR2_CID_SRATE].value); + switch (hdw->controls[PVR2_CID_SRATE].value) { + default: + case PVR2_CVAL_SRATE_48: + val = V4L2_AUDCLK_48_KHZ; + break; + case PVR2_CVAL_SRATE_44_1: + val = V4L2_AUDCLK_441_KHZ; + break; + } + ret = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_INT_AUDIO_CLOCK_FREQ,&val); + if (ret) return ret; + hdw->subsys_enabled_mask |= PVR2_SUBSYS_DIGITIZER_CFG_AUDIO; + return 0; +} + +int pvr2_decoder_set_norm(struct pvr2_hdw *hdw) +{ + v4l2_std_id std; + int status; + pvr2_decoder_trace("pvr2_decoder_set_norm %d", + hdw->controls[PVR2_CID_VIDEOSTANDARD].value); + switch (hdw->controls[PVR2_CID_VIDEOSTANDARD].value) { + default: + case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: + std = V4L2_STD_NTSC; + break; + case PVR2_CVAL_VIDEOSTANDARD_SECAM_L: + std = V4L2_STD_SECAM; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_I: + std = V4L2_STD_PAL_I; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_DK: + std = V4L2_STD_PAL_DK; + break; + case PVR2_CVAL_VIDEOSTANDARD_PAL_BG: + std = V4L2_STD_PAL_BG; + break; + } + status = pvr2_i2c_saa7115_cmd(hdw,VIDIOC_S_STD,&std); + if (status) return status; + hdw->subsys_enabled_mask |= PVR2_SUBSYS_DIGITIZER_CFG_NORM; + 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/v4l_experimental/pvrusb2/pvrusb2-video.h b/v4l_experimental/pvrusb2/pvrusb2-video.h new file mode 100644 index 000000000..8c081ccd2 --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2-video.h @@ -0,0 +1,55 @@ +/* + * + * $Id: pvrusb2-video.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_VIDEO_H +#define __PVRUSB2_VIDEO_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. + +*/ + +struct pvr2_hdw; + +int pvr2_decoder_set_norm(struct pvr2_hdw *); +int pvr2_decoder_set_input(struct pvr2_hdw *); +int pvr2_decoder_set_size(struct pvr2_hdw *); +int pvr2_decoder_set_audio(struct pvr2_hdw *); +int pvr2_decoder_set_bcsh(struct pvr2_hdw *); +int pvr2_decoder_is_tuned(struct pvr2_hdw *); +int pvr2_decoder_enable_output(struct pvr2_hdw *,int); + +#endif /* __PVRUSB2_VIDEO_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/v4l_experimental/pvrusb2/pvrusb2.h b/v4l_experimental/pvrusb2/pvrusb2.h new file mode 100644 index 000000000..f4e99ad3b --- /dev/null +++ b/v4l_experimental/pvrusb2/pvrusb2.h @@ -0,0 +1,43 @@ +/* + * + * $Id: pvrusb2.h,v 1.1 2005/11/14 13:31:24 mchehab Exp $ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_H +#define __PVRUSB2_H + +/* Maximum number of pvrusb2 instances we can track at once. You + might want to increase this - however the driver operation will not + be impaired if it is too small. Instead additional units just + won't have an ID assigned and it might not be possible to specify + module paramters for those extra units. */ +#define PVR_NUM 20 + +#endif /* __PVRUSB2_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ |