Commit d855497e authored by Mike Isely's avatar Mike Isely Committed by Mauro Carvalho Chehab

V4L/DVB (4228a): pvrusb2 to kernel 2.6.18

Implement V4L2 driver for the Hauppauge PVR USB2 TV tuner.

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