提交 062ef0d4 编写于 作者: E Ezequiel García 提交者: Mauro Carvalho Chehab

[media] staging: media: Remove easycap driver

This driver has been replaced by stk1160.
Signed-off-by: NEzequiel Garcia <elezegarcia@gmail.com>
Signed-off-by: NMauro Carvalho Chehab <mchehab@redhat.com>
上级 9cb2173e
......@@ -25,8 +25,6 @@ source "drivers/staging/media/cxd2099/Kconfig"
source "drivers/staging/media/dt3155v4l/Kconfig"
source "drivers/staging/media/easycap/Kconfig"
source "drivers/staging/media/go7007/Kconfig"
source "drivers/staging/media/solo6x10/Kconfig"
......
obj-$(CONFIG_DVB_AS102) += as102/
obj-$(CONFIG_DVB_CXD2099) += cxd2099/
obj-$(CONFIG_EASYCAP) += easycap/
obj-$(CONFIG_LIRC_STAGING) += lirc/
obj-$(CONFIG_SOLO6X10) += solo6x10/
obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l/
......
config EASYCAP
tristate "EasyCAP USB ID 05e1:0408 support"
depends on USB && VIDEO_DEV && SND
select SND_PCM
---help---
This is an integrated audio/video driver for EasyCAP cards with
USB ID 05e1:0408. It supports two hardware variants:
* EasyCAP USB 2.0 Video Adapter with Audio, Model DC60,
having input cables labelled CVBS, S-VIDEO, AUDIO(L), AUDIO(R)
* EasyCAP002 4-Channel USB 2.0 DVR, having input cables labelled
1, 2, 3, 4 and an unlabelled input cable for a microphone.
To compile this driver as a module, choose M here: the
module will be called easycap
config EASYCAP_DEBUG
bool "Enable EasyCAP driver debugging"
depends on EASYCAP
---help---
This option enables debug printouts
To enable debug, pass the debug level to the debug module
parameter:
modprobe easycap debug=[0..9]
easycap-objs := easycap_main.o
easycap-objs += easycap_low.o
easycap-objs += easycap_ioctl.o
easycap-objs += easycap_settings.o
easycap-objs += easycap_testcard.o
easycap-objs += easycap_sound.o
obj-$(CONFIG_EASYCAP) += easycap.o
ccflags-y := -Wall
***********************************************************
* EasyCAP USB 2.0 Video Adapter with Audio, Model DC60 *
* and *
* EasyCAP002 4-Channel USB 2.0 DVR *
***********************************************************
Mike Thomas <rmthomas@sciolus.org>
SUPPORTED HARDWARE
------------------
This driver is intended for use with hardware having USB ID 05e1:0408.
Two kinds of EasyCAP have this USB ID, namely:
* EasyCAP USB 2.0 Video Adapter with Audio, Model DC60,
having input cables labelled CVBS, S-VIDEO, AUDIO(L), AUDIO(R)
* EasyCAP002 4-Channel USB 2.0 DVR, having input cables labelled
1, 2, 3, 4 and an unlabelled input cable for a microphone.
BUILD OPTIONS AND DEPENDENCIES
------------------------------
Unless EASYCAP_DEBUG is defined during compilation it will not be possible
to select a debug level at the time of module installation.
KNOWN RUNTIME ISSUES
--------------------
(1) Intentionally, this driver will not stream material which is unambiguously
identified by the hardware as copy-protected. Normal video output will be
present for about a minute but will then freeze when this situation arises.
(2) The controls for luminance, contrast, saturation, hue and volume may not
always work properly.
(3) Reduced-resolution S-Video seems to suffer from moire artefacts.
INPUT NUMBERING
---------------
For the EasyCAP with S-VIDEO input cable the driver regards a request for
inputs numbered 0 or 1 as referring to CVBS and a request for input
numbered 5 as referring to S-VIDEO.
For the EasyCAP with four CVBS inputs the driver expects to be asked for
any one of inputs numbered 1,2,3,4. If input 0 is asked for, it is
interpreted as input 1.
MODULE PARAMETERS
-----------------
Three module parameters are defined:
debug the easycap module is configured at diagnostic level n (0 to 9)
gain audio gain level n (0 to 31, default is 16)
bars whether to display testcard bars when incoming video signal is lost
0 => no, 1 => yes (default)
SUPPORTED TV STANDARDS AND RESOLUTIONS
--------------------------------------
The following TV standards are natively supported by the hardware and are
usable as (for example) the "norm=" parameter in the mplayer command:
PAL_BGHIN, NTSC_N_443,
PAL_Nc, NTSC_N,
SECAM, NTSC_M, NTSC_M_JP,
PAL_60, NTSC_443,
PAL_M.
In addition, the driver offers "custom" pseudo-standards with a framerate
which is 20% of the usual framerate. These pseudo-standards are named:
PAL_BGHIN_SLOW, NTSC_N_443_SLOW,
PAL_Nc_SLOW, NTSC_N_SLOW,
SECAM_SLOW, NTSC_M_SLOW, NTSC_M_JP_SLOW,
PAL_60_SLOW, NTSC_443_SLOW,
PAL_M_SLOW.
The available picture sizes are:
at 25 frames per second: 720x576, 704x576, 640x480, 360x288, 320x240;
at 30 frames per second: 720x480, 640x480, 360x240, 320x240.
WHAT'S TESTED AND WHAT'S NOT
----------------------------
This driver is known to work with mplayer, mencoder, tvtime, zoneminder,
xawtv, gstreamer and sufficiently recent versions of vlc. An interface
to ffmpeg is implemented, but serious audio-video synchronization problems
remain.
The driver is designed to support all the TV standards accepted by the
hardware, but as yet it has actually been tested on only a few of these.
I have been unable to test and calibrate the S-video input myself because I
do not possess any equipment with S-video output.
UDEV RULES
----------
In order that the special files /dev/easycap0 and /dev/easysnd1 are created
with conveniently relaxed permissions when the EasyCAP is plugged in, a file
is preferably to be provided in directory /etc/udev/rules.d with content:
ACTION!="add|change", GOTO="easycap_rules_end"
ATTRS{idVendor}=="05e1", ATTRS{idProduct}=="0408", \
MODE="0666", OWNER="root", GROUP="root"
LABEL="easycap_rules_end"
MODPROBE CONFIGURATION
----------------------
The easycap module is in competition with the module snd-usb-audio for the
EasyCAP's audio channel, and its installation can be aided by providing a
file in directory /etc/modprobe.d with content:
options easycap gain=16 bars=1
install easycap /sbin/rmmod snd-usb-audio; /sbin/modprobe --ignore-install easycap
ACKNOWLEGEMENTS AND REFERENCES
------------------------------
This driver makes use of information contained in the Syntek Semicon DC-1125
Driver, presently maintained at http://sourceforge.net/projects/syntekdriver/
by Nicolas Vivien. Particularly useful has been a patch to the latter driver
provided by Ivor Hewitt in January 2009. The NTSC implementation is taken
from the work of Ben Trask.
/*****************************************************************************
* *
* easycap.h *
* *
*****************************************************************************/
/*
*
* Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org>
*
*
* This 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, or
* (at your option) any later version.
*
* The software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* THE FOLLOWING PARAMETERS ARE UNDEFINED:
*
* EASYCAP_DEBUG
*
* IF REQUIRED THEY MUST BE EXTERNALLY DEFINED, FOR EXAMPLE AS COMPILER
* OPTIONS.
*/
/*---------------------------------------------------------------------------*/
#ifndef __EASYCAP_H__
#define __EASYCAP_H__
/*---------------------------------------------------------------------------*/
/*
* THESE ARE NORMALLY DEFINED
*/
/*---------------------------------------------------------------------------*/
#define PATIENCE 500
#define PERSEVERE
/*---------------------------------------------------------------------------*/
/*
* THESE ARE FOR MAINTENANCE ONLY - NORMALLY UNDEFINED:
*/
/*---------------------------------------------------------------------------*/
#undef EASYCAP_TESTCARD
/*---------------------------------------------------------------------------*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kref.h>
#include <linux/usb.h>
#include <linux/uaccess.h>
#include <linux/i2c.h>
#include <linux/workqueue.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/sound.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/info.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#include <linux/videodev2.h>
#include <linux/soundcard.h>
/*---------------------------------------------------------------------------*/
/* VENDOR, PRODUCT: Syntek Semiconductor Co., Ltd
*
* EITHER EasyCAP USB 2.0 Video Adapter with Audio, Model No. DC60
* with input cabling: AUDIO(L), AUDIO(R), CVBS, S-VIDEO.
*
* OR EasyCAP 4CHANNEL USB 2.0 DVR, Model No. EasyCAP002
* with input cabling: MICROPHONE, CVBS1, CVBS2, CVBS3, CVBS4.
*/
/*---------------------------------------------------------------------------*/
#define USB_EASYCAP_VENDOR_ID 0x05e1
#define USB_EASYCAP_PRODUCT_ID 0x0408
#define EASYCAP_DRIVER_VERSION "0.9.01"
#define EASYCAP_DRIVER_DESCRIPTION "easycapdc60"
#define DONGLE_MANY 8
#define INPUT_MANY 6
/*---------------------------------------------------------------------------*/
/*
* DEFAULT LUMINANCE, CONTRAST, SATURATION AND HUE
*/
/*---------------------------------------------------------------------------*/
#define SAA_0A_DEFAULT 0x7F
#define SAA_0B_DEFAULT 0x3F
#define SAA_0C_DEFAULT 0x2F
#define SAA_0D_DEFAULT 0x00
/*---------------------------------------------------------------------------*/
/*
* VIDEO STREAMING PARAMETERS:
* USB 2.0 PROVIDES FOR HIGH-BANDWIDTH ENDPOINTS WITH AN UPPER LIMIT
* OF 3072 BYTES PER MICROFRAME for wMaxPacketSize.
*/
/*---------------------------------------------------------------------------*/
#define VIDEO_ISOC_BUFFER_MANY 16
#define VIDEO_ISOC_ORDER 3
#define VIDEO_ISOC_FRAMESPERDESC ((unsigned int) 1 << VIDEO_ISOC_ORDER)
#define USB_2_0_MAXPACKETSIZE 3072
#if (USB_2_0_MAXPACKETSIZE > PAGE_SIZE)
#error video_isoc_buffer[.] will not be big enough
#endif
#define VIDEO_JUNK_TOLERATE VIDEO_ISOC_BUFFER_MANY
#define VIDEO_LOST_TOLERATE 50
/*---------------------------------------------------------------------------*/
/*
* VIDEO BUFFERS
*/
/*---------------------------------------------------------------------------*/
#define FIELD_BUFFER_SIZE (203 * PAGE_SIZE)
#define FRAME_BUFFER_SIZE (405 * PAGE_SIZE)
#define FIELD_BUFFER_MANY 4
#define FRAME_BUFFER_MANY 6
/*---------------------------------------------------------------------------*/
/*
* AUDIO STREAMING PARAMETERS
*/
/*---------------------------------------------------------------------------*/
#define AUDIO_ISOC_BUFFER_MANY 16
#define AUDIO_ISOC_ORDER 1
#define AUDIO_ISOC_FRAMESPERDESC 32
#define AUDIO_ISOC_BUFFER_SIZE (PAGE_SIZE << AUDIO_ISOC_ORDER)
/*---------------------------------------------------------------------------*/
/*
* AUDIO BUFFERS
*/
/*---------------------------------------------------------------------------*/
#define AUDIO_FRAGMENT_MANY 32
#define PAGES_PER_AUDIO_FRAGMENT 4
/*---------------------------------------------------------------------------*/
/*
* IT IS ESSENTIAL THAT EVEN-NUMBERED STANDARDS ARE 25 FRAMES PER SECOND,
* ODD-NUMBERED STANDARDS ARE 30 FRAMES PER SECOND.
* THE NUMBERING OF STANDARDS MUST NOT BE CHANGED WITHOUT DUE CARE. NOT
* ONLY MUST THE PARAMETER
* STANDARD_MANY
* BE CHANGED TO CORRESPOND TO THE NEW NUMBER OF STANDARDS, BUT ALSO THE
* NUMBERING MUST REMAIN AN UNBROKEN ASCENDING SEQUENCE: DUMMY STANDARDS
* MAY NEED TO BE ADDED. APPROPRIATE CHANGES WILL ALWAYS BE REQUIRED IN
* ROUTINE fillin_formats() AND POSSIBLY ELSEWHERE. BEWARE.
*/
/*---------------------------------------------------------------------------*/
#define PAL_BGHIN 0
#define PAL_Nc 2
#define SECAM 4
#define NTSC_N 6
#define NTSC_N_443 8
#define NTSC_M 1
#define NTSC_443 3
#define NTSC_M_JP 5
#define PAL_60 7
#define PAL_M 9
#define PAL_BGHIN_SLOW 10
#define PAL_Nc_SLOW 12
#define SECAM_SLOW 14
#define NTSC_N_SLOW 16
#define NTSC_N_443_SLOW 18
#define NTSC_M_SLOW 11
#define NTSC_443_SLOW 13
#define NTSC_M_JP_SLOW 15
#define PAL_60_SLOW 17
#define PAL_M_SLOW 19
#define STANDARD_MANY 20
/*---------------------------------------------------------------------------*/
/*
* ENUMS
*/
/*---------------------------------------------------------------------------*/
enum {
AT_720x576,
AT_704x576,
AT_640x480,
AT_720x480,
AT_360x288,
AT_320x240,
AT_360x240,
RESOLUTION_MANY
};
enum {
FMT_UYVY,
FMT_YUY2,
FMT_RGB24,
FMT_RGB32,
FMT_BGR24,
FMT_BGR32,
PIXELFORMAT_MANY
};
enum {
FIELD_NONE,
FIELD_INTERLACED,
INTERLACE_MANY
};
#define SETTINGS_MANY (STANDARD_MANY * \
RESOLUTION_MANY * \
2 * \
PIXELFORMAT_MANY * \
INTERLACE_MANY)
/*---------------------------------------------------------------------------*/
/*
* STRUCTURE DEFINITIONS
*/
/*---------------------------------------------------------------------------*/
struct easycap_dongle {
struct easycap *peasycap;
struct mutex mutex_video;
struct mutex mutex_audio;
};
/*---------------------------------------------------------------------------*/
struct data_buffer {
struct list_head list_head;
void *pgo;
void *pto;
u16 kount;
u16 input;
};
/*---------------------------------------------------------------------------*/
struct data_urb {
struct list_head list_head;
struct urb *purb;
int isbuf;
int length;
};
/*---------------------------------------------------------------------------*/
struct easycap_standard {
u16 mask;
struct v4l2_standard v4l2_standard;
};
struct easycap_format {
u16 mask;
char name[128];
struct v4l2_format v4l2_format;
};
struct inputset {
int input;
int input_ok;
int standard_offset;
int standard_offset_ok;
int format_offset;
int format_offset_ok;
int brightness;
int brightness_ok;
int contrast;
int contrast_ok;
int saturation;
int saturation_ok;
int hue;
int hue_ok;
};
/*---------------------------------------------------------------------------*/
/*
* easycap.ilk == 0 => CVBS+S-VIDEO HARDWARE, AUDIO wMaxPacketSize=256
* easycap.ilk == 2 => CVBS+S-VIDEO HARDWARE, AUDIO wMaxPacketSize=9
* easycap.ilk == 3 => FOUR-CVBS HARDWARE, AUDIO wMaxPacketSize=9
*/
/*---------------------------------------------------------------------------*/
struct easycap {
int isdongle;
int minor;
struct video_device video_device;
struct v4l2_device v4l2_device;
int status;
unsigned int audio_pages_per_fragment;
unsigned int audio_bytes_per_fragment;
unsigned int audio_buffer_page_many;
#define UPSAMPLE
#ifdef UPSAMPLE
s16 oldaudio;
#endif /*UPSAMPLE*/
int ilk;
bool microphone;
struct usb_device *pusb_device;
struct usb_interface *pusb_interface;
struct kref kref;
int queued[FRAME_BUFFER_MANY];
int done[FRAME_BUFFER_MANY];
wait_queue_head_t wq_video;
wait_queue_head_t wq_audio;
wait_queue_head_t wq_trigger;
int input;
int polled;
int standard_offset;
int format_offset;
struct inputset inputset[INPUT_MANY];
bool ntsc;
int fps;
int usec;
int tolerate;
int skip;
int skipped;
int lost[INPUT_MANY];
int merit[180];
int video_interface;
int video_altsetting_on;
int video_altsetting_off;
int video_endpointnumber;
int video_isoc_maxframesize;
int video_isoc_buffer_size;
int video_isoc_framesperdesc;
int video_isoc_streaming;
int video_isoc_sequence;
int video_idle;
int video_eof;
int video_junk;
struct data_buffer video_isoc_buffer[VIDEO_ISOC_BUFFER_MANY];
struct data_buffer field_buffer[FIELD_BUFFER_MANY]
[(FIELD_BUFFER_SIZE/PAGE_SIZE)];
struct data_buffer frame_buffer[FRAME_BUFFER_MANY]
[(FRAME_BUFFER_SIZE/PAGE_SIZE)];
struct list_head urb_video_head;
struct list_head *purb_video_head;
u8 cache[8];
u8 *pcache;
int video_mt;
int audio_mt;
u32 isequence;
int vma_many;
/*---------------------------------------------------------------------------*/
/*
* BUFFER INDICATORS
*/
/*---------------------------------------------------------------------------*/
int field_fill; /* Field buffer being filled by easycap_complete(). */
/* Bumped only by easycap_complete(). */
int field_page; /* Page of field buffer page being filled by */
/* easycap_complete(). */
int field_read; /* Field buffer to be read by field2frame(). */
/* Bumped only by easycap_complete(). */
int frame_fill; /* Frame buffer being filled by field2frame(). */
/* Bumped only by easycap_dqbuf() when */
/* field2frame() has created a complete frame. */
int frame_read; /* Frame buffer offered to user by DQBUF. */
/* Set only by easycap_dqbuf() to trail frame_fill.*/
int frame_lock; /* Flag set to 1 by DQBUF and cleared by QBUF */
/*---------------------------------------------------------------------------*/
/*
* IMAGE PROPERTIES
*/
/*---------------------------------------------------------------------------*/
u32 pixelformat;
int width;
int height;
int bytesperpixel;
bool byteswaporder;
bool decimatepixel;
bool offerfields;
int frame_buffer_used;
int frame_buffer_many;
int videofieldamount;
int brightness;
int contrast;
int saturation;
int hue;
int allocation_video_urb;
int allocation_video_page;
int allocation_video_struct;
int registered_video;
/*---------------------------------------------------------------------------*/
/*
* ALSA
*/
/*---------------------------------------------------------------------------*/
struct snd_pcm_hardware alsa_hardware;
struct snd_card *psnd_card;
struct snd_pcm *psnd_pcm;
struct snd_pcm_substream *psubstream;
int dma_fill;
int dma_next;
int dma_read;
/*---------------------------------------------------------------------------*/
/*
* SOUND PROPERTIES
*/
/*---------------------------------------------------------------------------*/
int audio_interface;
int audio_altsetting_on;
int audio_altsetting_off;
int audio_endpointnumber;
int audio_isoc_maxframesize;
int audio_isoc_buffer_size;
int audio_isoc_framesperdesc;
int audio_isoc_streaming;
int audio_idle;
int audio_eof;
int volume;
int mute;
s8 gain;
struct data_buffer audio_isoc_buffer[AUDIO_ISOC_BUFFER_MANY];
struct list_head urb_audio_head;
struct list_head *purb_audio_head;
/*---------------------------------------------------------------------------*/
/*
* BUFFER INDICATORS
*/
/*---------------------------------------------------------------------------*/
int audio_fill; /* Audio buffer being filled by easycap_complete(). */
/* Bumped only by easycap_complete(). */
int audio_read; /* Audio buffer page being read by easycap_read(). */
/* Set by easycap_read() to trail audio_fill by */
/* one fragment. */
/*---------------------------------------------------------------------------*/
/*
* SOUND PROPERTIES
*/
/*---------------------------------------------------------------------------*/
int allocation_audio_urb;
int allocation_audio_page;
int allocation_audio_struct;
int registered_audio;
long long int audio_sample;
long long int audio_niveau;
long long int audio_square;
struct data_buffer audio_buffer[];
};
/*---------------------------------------------------------------------------*/
/*
* VIDEO FUNCTION PROTOTYPES
*/
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
int easycap_newinput(struct easycap *, int);
void easycap_testcard(struct easycap *, int);
int easycap_isdongle(struct easycap *);
long easycap_unlocked_ioctl(struct file *, unsigned int, unsigned long);
int easycap_video_dqbuf(struct easycap *, int);
int easycap_video_submit_urbs(struct easycap *);
int easycap_video_kill_urbs(struct easycap *);
int easycap_video_fillin_formats(void);
int adjust_standard(struct easycap *, v4l2_std_id);
int adjust_format(struct easycap *, u32, u32, u32, int, bool);
int adjust_brightness(struct easycap *, int);
int adjust_contrast(struct easycap *, int);
int adjust_saturation(struct easycap *, int);
int adjust_hue(struct easycap *, int);
/*---------------------------------------------------------------------------*/
/*
* AUDIO FUNCTION PROTOTYPES
*/
/*---------------------------------------------------------------------------*/
int easycap_alsa_probe(struct easycap *);
int easycap_audio_kill_urbs(struct easycap *);
void easycap_alsa_complete(struct urb *);
/*---------------------------------------------------------------------------*/
/*
* LOW-LEVEL FUNCTION PROTOTYPES
*/
/*---------------------------------------------------------------------------*/
int easycap_audio_gainset(struct usb_device *, s8);
int easycap_audio_setup(struct easycap *);
int easycap_wakeup_device(struct usb_device *);
int setup_stk(struct usb_device *, bool);
int setup_saa(struct usb_device *, bool);
int ready_saa(struct usb_device *);
int merit_saa(struct usb_device *);
int check_vt(struct usb_device *);
int select_input(struct usb_device *, int, int);
int set_resolution(struct usb_device *, u16, u16, u16, u16);
int read_saa(struct usb_device *, u16);
int write_saa(struct usb_device *, u16, u16);
int start_100(struct usb_device *);
int stop_100(struct usb_device *);
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/*
* MACROS SAM(...) AND JOM(...) ALLOW DIAGNOSTIC OUTPUT TO BE TAGGED WITH
* THE IDENTITY OF THE DONGLE TO WHICH IT APPLIES, BUT IF INVOKED WHEN THE
* POINTER peasycap IS INVALID AN Oops IS LIKELY, AND ITS CAUSE MAY NOT BE
* IMMEDIATELY OBVIOUS FROM A CASUAL READING OF THE SOURCE CODE. BEWARE.
*/
/*---------------------------------------------------------------------------*/
const char *strerror(int err);
#define SAY(format, args...) do { \
printk(KERN_DEBUG "easycap:: %s: " \
format, __func__, ##args); \
} while (0)
#define SAM(format, args...) do { \
printk(KERN_DEBUG "easycap::%i%s: " \
format, peasycap->isdongle, __func__, ##args);\
} while (0)
#ifdef CONFIG_EASYCAP_DEBUG
extern int easycap_debug;
#define JOT(n, format, args...) do { \
if (n <= easycap_debug) { \
printk(KERN_DEBUG "easycap:: %s: " \
format, __func__, ##args);\
} \
} while (0)
#define JOM(n, format, args...) do { \
if (n <= easycap_debug) { \
printk(KERN_DEBUG "easycap::%i%s: " \
format, peasycap->isdongle, __func__, ##args);\
} \
} while (0)
#else
#define JOT(n, format, args...) do {} while (0)
#define JOM(n, format, args...) do {} while (0)
#endif /* CONFIG_EASYCAP_DEBUG */
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/* globals
*/
/*---------------------------------------------------------------------------*/
extern bool easycap_readback;
extern const struct easycap_standard easycap_standard[];
extern struct easycap_format easycap_format[];
extern struct v4l2_queryctrl easycap_control[];
extern struct easycap_dongle easycapdc60_dongle[];
#endif /* !__EASYCAP_H__ */
/******************************************************************************
* *
* easycap_ioctl.c *
* *
******************************************************************************/
/*
*
* Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org>
*
*
* This 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, or
* (at your option) any later version.
*
* The software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*****************************************************************************/
#include "easycap.h"
#include <linux/version.h>
/*--------------------------------------------------------------------------*/
/*
* UNLESS THERE IS A PREMATURE ERROR RETURN THIS ROUTINE UPDATES THE
* FOLLOWING:
* peasycap->standard_offset
* peasycap->inputset[peasycap->input].standard_offset
* peasycap->fps
* peasycap->usec
* peasycap->tolerate
* peasycap->skip
*/
/*---------------------------------------------------------------------------*/
int adjust_standard(struct easycap *peasycap, v4l2_std_id std_id)
{
struct easycap_standard const *peasycap_standard;
u16 reg, set;
int ir, rc, need, k;
unsigned int itwas, isnow;
bool resubmit;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
peasycap_standard = &easycap_standard[0];
while (0xFFFF != peasycap_standard->mask) {
if (std_id == peasycap_standard->v4l2_standard.id)
break;
peasycap_standard++;
}
if (0xFFFF == peasycap_standard->mask) {
peasycap_standard = &easycap_standard[0];
while (0xFFFF != peasycap_standard->mask) {
if (std_id & peasycap_standard->v4l2_standard.id)
break;
peasycap_standard++;
}
}
if (0xFFFF == peasycap_standard->mask) {
SAM("ERROR: 0x%08X=std_id: standard not found\n",
(unsigned int)std_id);
return -EINVAL;
}
SAM("selected standard: %s\n",
&(peasycap_standard->v4l2_standard.name[0]));
if (peasycap->standard_offset == peasycap_standard - easycap_standard) {
SAM("requested standard already in effect\n");
return 0;
}
peasycap->standard_offset = peasycap_standard - easycap_standard;
for (k = 0; k < INPUT_MANY; k++) {
if (!peasycap->inputset[k].standard_offset_ok) {
peasycap->inputset[k].standard_offset =
peasycap->standard_offset;
}
}
if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) {
peasycap->inputset[peasycap->input].standard_offset =
peasycap->standard_offset;
peasycap->inputset[peasycap->input].standard_offset_ok = 1;
} else
JOM(8, "%i=peasycap->input\n", peasycap->input);
peasycap->fps = peasycap_standard->v4l2_standard.frameperiod.denominator /
peasycap_standard->v4l2_standard.frameperiod.numerator;
switch (peasycap->fps) {
case 6:
case 30: {
peasycap->ntsc = true;
break;
}
case 5:
case 25: {
peasycap->ntsc = false;
break;
}
default: {
SAM("MISTAKE: %i=frames-per-second\n", peasycap->fps);
return -ENOENT;
}
}
JOM(8, "%i frames-per-second\n", peasycap->fps);
if (0x8000 & peasycap_standard->mask) {
peasycap->skip = 5;
peasycap->usec = 1000000 / (2 * (5 * peasycap->fps));
peasycap->tolerate = 1000 * (25 / (5 * peasycap->fps));
} else {
peasycap->skip = 0;
peasycap->usec = 1000000 / (2 * peasycap->fps);
peasycap->tolerate = 1000 * (25 / peasycap->fps);
}
if (peasycap->video_isoc_streaming) {
resubmit = true;
easycap_video_kill_urbs(peasycap);
} else
resubmit = false;
/*--------------------------------------------------------------------------*/
/*
* SAA7113H DATASHEET PAGE 44, TABLE 42
*/
/*--------------------------------------------------------------------------*/
need = 0;
itwas = 0;
reg = 0x00;
set = 0x00;
switch (peasycap_standard->mask & 0x000F) {
case NTSC_M_JP: {
reg = 0x0A;
set = 0x95;
ir = read_saa(peasycap->pusb_device, reg);
if (0 > ir)
SAM("ERROR: cannot read SAA register 0x%02X\n", reg);
else
itwas = (unsigned int)ir;
rc = write_saa(peasycap->pusb_device, reg, set);
if (rc)
SAM("ERROR: failed to set SAA register "
"0x%02X to 0x%02X for JP standard\n", reg, set);
else {
isnow = (unsigned int)read_saa(peasycap->pusb_device, reg);
if (0 > ir)
JOM(8, "SAA register 0x%02X changed "
"to 0x%02X\n", reg, isnow);
else
JOM(8, "SAA register 0x%02X changed "
"from 0x%02X to 0x%02X\n", reg, itwas, isnow);
}
reg = 0x0B;
set = 0x48;
ir = read_saa(peasycap->pusb_device, reg);
if (0 > ir)
SAM("ERROR: cannot read SAA register 0x%02X\n", reg);
else
itwas = (unsigned int)ir;
rc = write_saa(peasycap->pusb_device, reg, set);
if (rc)
SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X "
"for JP standard\n", reg, set);
else {
isnow = (unsigned int)read_saa(peasycap->pusb_device, reg);
if (0 > ir)
JOM(8, "SAA register 0x%02X changed "
"to 0x%02X\n", reg, isnow);
else
JOM(8, "SAA register 0x%02X changed "
"from 0x%02X to 0x%02X\n", reg, itwas, isnow);
}
/*--------------------------------------------------------------------------*/
/*
* NOTE: NO break HERE: RUN ON TO NEXT CASE
*/
/*--------------------------------------------------------------------------*/
}
case NTSC_M:
case PAL_BGHIN: {
reg = 0x0E;
set = 0x01;
need = 1;
break;
}
case NTSC_N_443:
case PAL_60: {
reg = 0x0E;
set = 0x11;
need = 1;
break;
}
case NTSC_443:
case PAL_Nc: {
reg = 0x0E;
set = 0x21;
need = 1;
break;
}
case NTSC_N:
case PAL_M: {
reg = 0x0E;
set = 0x31;
need = 1;
break;
}
case SECAM: {
reg = 0x0E;
set = 0x51;
need = 1;
break;
}
default:
break;
}
/*--------------------------------------------------------------------------*/
if (need) {
ir = read_saa(peasycap->pusb_device, reg);
if (0 > ir)
SAM("ERROR: failed to read SAA register 0x%02X\n", reg);
else
itwas = (unsigned int)ir;
rc = write_saa(peasycap->pusb_device, reg, set);
if (0 != write_saa(peasycap->pusb_device, reg, set)) {
SAM("ERROR: failed to set SAA register "
"0x%02X to 0x%02X for table 42\n", reg, set);
} else {
isnow = (unsigned int)read_saa(peasycap->pusb_device, reg);
if (0 > ir)
JOM(8, "SAA register 0x%02X changed "
"to 0x%02X\n", reg, isnow);
else
JOM(8, "SAA register 0x%02X changed "
"from 0x%02X to 0x%02X\n", reg, itwas, isnow);
}
}
/*--------------------------------------------------------------------------*/
/*
* SAA7113H DATASHEET PAGE 41
*/
/*--------------------------------------------------------------------------*/
reg = 0x08;
ir = read_saa(peasycap->pusb_device, reg);
if (0 > ir)
SAM("ERROR: failed to read SAA register 0x%02X "
"so cannot reset\n", reg);
else {
itwas = (unsigned int)ir;
if (peasycap_standard->mask & 0x0001)
set = itwas | 0x40 ;
else
set = itwas & ~0x40 ;
rc = write_saa(peasycap->pusb_device, reg, set);
if (rc)
SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n",
reg, set);
else {
isnow = (unsigned int)read_saa(peasycap->pusb_device, reg);
if (0 > ir)
JOM(8, "SAA register 0x%02X changed to 0x%02X\n",
reg, isnow);
else
JOM(8, "SAA register 0x%02X changed "
"from 0x%02X to 0x%02X\n", reg, itwas, isnow);
}
}
/*--------------------------------------------------------------------------*/
/*
* SAA7113H DATASHEET PAGE 51, TABLE 57
*/
/*---------------------------------------------------------------------------*/
reg = 0x40;
ir = read_saa(peasycap->pusb_device, reg);
if (0 > ir)
SAM("ERROR: failed to read SAA register 0x%02X "
"so cannot reset\n", reg);
else {
itwas = (unsigned int)ir;
if (peasycap_standard->mask & 0x0001)
set = itwas | 0x80 ;
else
set = itwas & ~0x80 ;
rc = write_saa(peasycap->pusb_device, reg, set);
if (rc)
SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n",
reg, set);
else {
isnow = (unsigned int)read_saa(peasycap->pusb_device, reg);
if (0 > ir)
JOM(8, "SAA register 0x%02X changed to 0x%02X\n",
reg, isnow);
else
JOM(8, "SAA register 0x%02X changed "
"from 0x%02X to 0x%02X\n", reg, itwas, isnow);
}
}
/*--------------------------------------------------------------------------*/
/*
* SAA7113H DATASHEET PAGE 53, TABLE 66
*/
/*--------------------------------------------------------------------------*/
reg = 0x5A;
ir = read_saa(peasycap->pusb_device, reg);
if (0 > ir)
SAM("ERROR: failed to read SAA register 0x%02X but continuing\n", reg);
itwas = (unsigned int)ir;
if (peasycap_standard->mask & 0x0001)
set = 0x0A ;
else
set = 0x07 ;
if (0 != write_saa(peasycap->pusb_device, reg, set))
SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n",
reg, set);
else {
isnow = (unsigned int)read_saa(peasycap->pusb_device, reg);
if (0 > ir)
JOM(8, "SAA register 0x%02X changed "
"to 0x%02X\n", reg, isnow);
else
JOM(8, "SAA register 0x%02X changed "
"from 0x%02X to 0x%02X\n", reg, itwas, isnow);
}
if (resubmit)
easycap_video_submit_urbs(peasycap);
return 0;
}
/*****************************************************************************/
/*--------------------------------------------------------------------------*/
/*
* THE ALGORITHM FOR RESPONDING TO THE VIDIO_S_FMT IOCTL REQUIRES
* A VALID VALUE OF peasycap->standard_offset, OTHERWISE -EBUSY IS RETURNED.
*
* PROVIDED THE ARGUMENT try IS false AND THERE IS NO PREMATURE ERROR RETURN
* THIS ROUTINE UPDATES THE FOLLOWING:
* peasycap->format_offset
* peasycap->inputset[peasycap->input].format_offset
* peasycap->pixelformat
* peasycap->height
* peasycap->width
* peasycap->bytesperpixel
* peasycap->byteswaporder
* peasycap->decimatepixel
* peasycap->frame_buffer_used
* peasycap->videofieldamount
* peasycap->offerfields
*
* IF SUCCESSFUL THE FUNCTION RETURNS THE OFFSET IN easycap_format[]
* IDENTIFYING THE FORMAT WHICH IS TO RETURNED TO THE USER.
* ERRORS RETURN A NEGATIVE NUMBER.
*/
/*--------------------------------------------------------------------------*/
int adjust_format(struct easycap *peasycap,
u32 width, u32 height, u32 pixelformat, int field, bool try)
{
struct easycap_format *peasycap_format, *peasycap_best_format;
u16 mask;
struct usb_device *p;
int miss, multiplier, best, k;
char bf[5], fo[32], *pc;
u32 uc;
bool resubmit;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (0 > peasycap->standard_offset) {
JOM(8, "%i=peasycap->standard_offset\n", peasycap->standard_offset);
return -EBUSY;
}
p = peasycap->pusb_device;
if (!p) {
SAM("ERROR: peaycap->pusb_device is NULL\n");
return -EFAULT;
}
pc = &bf[0];
uc = pixelformat;
memcpy((void *)pc, (void *)(&uc), 4);
bf[4] = 0;
mask = 0xFF & easycap_standard[peasycap->standard_offset].mask;
SAM("sought: %ix%i,%s(0x%08X),%i=field,0x%02X=std mask\n",
width, height, pc, pixelformat, field, mask);
switch (field) {
case V4L2_FIELD_ANY: {
strcpy(&fo[0], "V4L2_FIELD_ANY ");
break;
}
case V4L2_FIELD_NONE: {
strcpy(&fo[0], "V4L2_FIELD_NONE");
break;
}
case V4L2_FIELD_TOP: {
strcpy(&fo[0], "V4L2_FIELD_TOP");
break;
}
case V4L2_FIELD_BOTTOM: {
strcpy(&fo[0], "V4L2_FIELD_BOTTOM");
break;
}
case V4L2_FIELD_INTERLACED: {
strcpy(&fo[0], "V4L2_FIELD_INTERLACED");
break;
}
case V4L2_FIELD_SEQ_TB: {
strcpy(&fo[0], "V4L2_FIELD_SEQ_TB");
break;
}
case V4L2_FIELD_SEQ_BT: {
strcpy(&fo[0], "V4L2_FIELD_SEQ_BT");
break;
}
case V4L2_FIELD_ALTERNATE: {
strcpy(&fo[0], "V4L2_FIELD_ALTERNATE");
break;
}
case V4L2_FIELD_INTERLACED_TB: {
strcpy(&fo[0], "V4L2_FIELD_INTERLACED_TB");
break;
}
case V4L2_FIELD_INTERLACED_BT: {
strcpy(&fo[0], "V4L2_FIELD_INTERLACED_BT");
break;
}
default: {
strcpy(&fo[0], "V4L2_FIELD_... UNKNOWN ");
break;
}
}
SAM("sought: %s\n", &fo[0]);
if (V4L2_FIELD_ANY == field) {
field = V4L2_FIELD_NONE;
SAM("prefer: V4L2_FIELD_NONE=field, was V4L2_FIELD_ANY\n");
}
peasycap_best_format = NULL;
peasycap_format = &easycap_format[0];
while (0 != peasycap_format->v4l2_format.fmt.pix.width) {
JOM(16, ".> %i %i 0x%08X %ix%i\n",
peasycap_format->mask & 0x01,
peasycap_format->v4l2_format.fmt.pix.field,
peasycap_format->v4l2_format.fmt.pix.pixelformat,
peasycap_format->v4l2_format.fmt.pix.width,
peasycap_format->v4l2_format.fmt.pix.height);
if (((peasycap_format->mask & 0x1F) == (mask & 0x1F)) &&
(peasycap_format->v4l2_format.fmt.pix.field == field) &&
(peasycap_format->v4l2_format.fmt.pix.pixelformat == pixelformat) &&
(peasycap_format->v4l2_format.fmt.pix.width == width) &&
(peasycap_format->v4l2_format.fmt.pix.height == height)) {
peasycap_best_format = peasycap_format;
break;
}
peasycap_format++;
}
if (0 == peasycap_format->v4l2_format.fmt.pix.width) {
SAM("cannot do: %ix%i with standard mask 0x%02X\n",
width, height, mask);
peasycap_format = &easycap_format[0];
best = -1;
while (0 != peasycap_format->v4l2_format.fmt.pix.width) {
if (((peasycap_format->mask & 0x1F) == (mask & 0x1F)) &&
(peasycap_format->v4l2_format.fmt.pix.field == field) &&
(peasycap_format->v4l2_format.fmt.pix.pixelformat == pixelformat)) {
miss = abs(peasycap_format->v4l2_format.fmt.pix.width - width);
if ((best > miss) || (best < 0)) {
best = miss;
peasycap_best_format = peasycap_format;
if (!miss)
break;
}
}
peasycap_format++;
}
if (-1 == best) {
SAM("cannot do %ix... with standard mask 0x%02X\n",
width, mask);
SAM("cannot do ...x%i with standard mask 0x%02X\n",
height, mask);
SAM(" %ix%i unmatched\n", width, height);
return peasycap->format_offset;
}
}
if (!peasycap_best_format) {
SAM("MISTAKE: peasycap_best_format is NULL");
return -EINVAL;
}
peasycap_format = peasycap_best_format;
/*...........................................................................*/
if (try)
return peasycap_best_format - easycap_format;
/*...........................................................................*/
if (false != try) {
SAM("MISTAKE: true==try where is should be false\n");
return -EINVAL;
}
SAM("actioning: %ix%i %s\n",
peasycap_format->v4l2_format.fmt.pix.width,
peasycap_format->v4l2_format.fmt.pix.height,
&peasycap_format->name[0]);
peasycap->height = peasycap_format->v4l2_format.fmt.pix.height;
peasycap->width = peasycap_format->v4l2_format.fmt.pix.width;
peasycap->pixelformat = peasycap_format->v4l2_format.fmt.pix.pixelformat;
peasycap->format_offset = peasycap_format - easycap_format;
for (k = 0; k < INPUT_MANY; k++) {
if (!peasycap->inputset[k].format_offset_ok) {
peasycap->inputset[k].format_offset =
peasycap->format_offset;
}
}
if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) {
peasycap->inputset[peasycap->input].format_offset =
peasycap->format_offset;
peasycap->inputset[peasycap->input].format_offset_ok = 1;
} else
JOM(8, "%i=peasycap->input\n", peasycap->input);
peasycap->bytesperpixel = (0x00E0 & peasycap_format->mask) >> 5 ;
if (0x0100 & peasycap_format->mask)
peasycap->byteswaporder = true;
else
peasycap->byteswaporder = false;
if (0x0200 & peasycap_format->mask)
peasycap->skip = 5;
else
peasycap->skip = 0;
if (0x0800 & peasycap_format->mask)
peasycap->decimatepixel = true;
else
peasycap->decimatepixel = false;
if (0x1000 & peasycap_format->mask)
peasycap->offerfields = true;
else
peasycap->offerfields = false;
if (peasycap->decimatepixel)
multiplier = 2;
else
multiplier = 1;
peasycap->videofieldamount =
multiplier * peasycap->width * multiplier * peasycap->height;
peasycap->frame_buffer_used =
peasycap->bytesperpixel * peasycap->width * peasycap->height;
if (peasycap->video_isoc_streaming) {
resubmit = true;
easycap_video_kill_urbs(peasycap);
} else
resubmit = false;
/*---------------------------------------------------------------------------*/
/*
* PAL
*/
/*---------------------------------------------------------------------------*/
if (0 == (0x01 & peasycap_format->mask)) {
if (((720 == peasycap_format->v4l2_format.fmt.pix.width) &&
(576 == peasycap_format->v4l2_format.fmt.pix.height)) ||
((360 == peasycap_format->v4l2_format.fmt.pix.width) &&
(288 == peasycap_format->v4l2_format.fmt.pix.height))) {
if (set_resolution(p, 0x0000, 0x0001, 0x05A0, 0x0121)) {
SAM("ERROR: set_resolution() failed\n");
return -EINVAL;
}
} else if ((704 == peasycap_format->v4l2_format.fmt.pix.width) &&
(576 == peasycap_format->v4l2_format.fmt.pix.height)) {
if (set_resolution(p, 0x0004, 0x0001, 0x0584, 0x0121)) {
SAM("ERROR: set_resolution() failed\n");
return -EINVAL;
}
} else if (((640 == peasycap_format->v4l2_format.fmt.pix.width) &&
(480 == peasycap_format->v4l2_format.fmt.pix.height)) ||
((320 == peasycap_format->v4l2_format.fmt.pix.width) &&
(240 == peasycap_format->v4l2_format.fmt.pix.height))) {
if (set_resolution(p, 0x0014, 0x0020, 0x0514, 0x0110)) {
SAM("ERROR: set_resolution() failed\n");
return -EINVAL;
}
} else {
SAM("MISTAKE: bad format, cannot set resolution\n");
return -EINVAL;
}
/*---------------------------------------------------------------------------*/
/*
* NTSC
*/
/*---------------------------------------------------------------------------*/
} else {
if (((720 == peasycap_format->v4l2_format.fmt.pix.width) &&
(480 == peasycap_format->v4l2_format.fmt.pix.height)) ||
((360 == peasycap_format->v4l2_format.fmt.pix.width) &&
(240 == peasycap_format->v4l2_format.fmt.pix.height))) {
if (set_resolution(p, 0x0000, 0x0003, 0x05A0, 0x00F3)) {
SAM("ERROR: set_resolution() failed\n");
return -EINVAL;
}
} else if (((640 == peasycap_format->v4l2_format.fmt.pix.width) &&
(480 == peasycap_format->v4l2_format.fmt.pix.height)) ||
((320 == peasycap_format->v4l2_format.fmt.pix.width) &&
(240 == peasycap_format->v4l2_format.fmt.pix.height))) {
if (set_resolution(p, 0x0014, 0x0003, 0x0514, 0x00F3)) {
SAM("ERROR: set_resolution() failed\n");
return -EINVAL;
}
} else {
SAM("MISTAKE: bad format, cannot set resolution\n");
return -EINVAL;
}
}
/*---------------------------------------------------------------------------*/
if (resubmit)
easycap_video_submit_urbs(peasycap);
return peasycap_best_format - easycap_format;
}
/*****************************************************************************/
int adjust_brightness(struct easycap *peasycap, int value)
{
unsigned int mood;
int i1, k;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
i1 = 0;
while (0xFFFFFFFF != easycap_control[i1].id) {
if (V4L2_CID_BRIGHTNESS == easycap_control[i1].id) {
if ((easycap_control[i1].minimum > value) ||
(easycap_control[i1].maximum < value))
value = easycap_control[i1].default_value;
if ((easycap_control[i1].minimum <= peasycap->brightness) &&
(easycap_control[i1].maximum >= peasycap->brightness)) {
if (peasycap->brightness == value) {
SAM("unchanged brightness at 0x%02X\n",
value);
return 0;
}
}
peasycap->brightness = value;
for (k = 0; k < INPUT_MANY; k++) {
if (!peasycap->inputset[k].brightness_ok)
peasycap->inputset[k].brightness =
peasycap->brightness;
}
if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) {
peasycap->inputset[peasycap->input].brightness =
peasycap->brightness;
peasycap->inputset[peasycap->input].brightness_ok = 1;
} else
JOM(8, "%i=peasycap->input\n", peasycap->input);
mood = 0x00FF & (unsigned int)peasycap->brightness;
if (write_saa(peasycap->pusb_device, 0x0A, mood)) {
SAM("WARNING: failed to adjust brightness "
"to 0x%02X\n", mood);
return -ENOENT;
}
SAM("adjusting brightness to 0x%02X\n", mood);
return 0;
}
i1++;
}
SAM("WARNING: failed to adjust brightness: control not found\n");
return -ENOENT;
}
/*****************************************************************************/
int adjust_contrast(struct easycap *peasycap, int value)
{
unsigned int mood;
int i1, k;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
i1 = 0;
while (0xFFFFFFFF != easycap_control[i1].id) {
if (V4L2_CID_CONTRAST == easycap_control[i1].id) {
if ((easycap_control[i1].minimum > value) ||
(easycap_control[i1].maximum < value))
value = easycap_control[i1].default_value;
if ((easycap_control[i1].minimum <= peasycap->contrast) &&
(easycap_control[i1].maximum >= peasycap->contrast)) {
if (peasycap->contrast == value) {
SAM("unchanged contrast at 0x%02X\n", value);
return 0;
}
}
peasycap->contrast = value;
for (k = 0; k < INPUT_MANY; k++) {
if (!peasycap->inputset[k].contrast_ok)
peasycap->inputset[k].contrast = peasycap->contrast;
}
if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) {
peasycap->inputset[peasycap->input].contrast =
peasycap->contrast;
peasycap->inputset[peasycap->input].contrast_ok = 1;
} else
JOM(8, "%i=peasycap->input\n", peasycap->input);
mood = 0x00FF & (unsigned int) (peasycap->contrast - 128);
if (write_saa(peasycap->pusb_device, 0x0B, mood)) {
SAM("WARNING: failed to adjust contrast to "
"0x%02X\n", mood);
return -ENOENT;
}
SAM("adjusting contrast to 0x%02X\n", mood);
return 0;
}
i1++;
}
SAM("WARNING: failed to adjust contrast: control not found\n");
return -ENOENT;
}
/*****************************************************************************/
int adjust_saturation(struct easycap *peasycap, int value)
{
unsigned int mood;
int i1, k;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
i1 = 0;
while (0xFFFFFFFF != easycap_control[i1].id) {
if (V4L2_CID_SATURATION == easycap_control[i1].id) {
if ((easycap_control[i1].minimum > value) ||
(easycap_control[i1].maximum < value))
value = easycap_control[i1].default_value;
if ((easycap_control[i1].minimum <= peasycap->saturation) &&
(easycap_control[i1].maximum >= peasycap->saturation)) {
if (peasycap->saturation == value) {
SAM("unchanged saturation at 0x%02X\n",
value);
return 0;
}
}
peasycap->saturation = value;
for (k = 0; k < INPUT_MANY; k++) {
if (!peasycap->inputset[k].saturation_ok)
peasycap->inputset[k].saturation =
peasycap->saturation;
}
if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) {
peasycap->inputset[peasycap->input].saturation =
peasycap->saturation;
peasycap->inputset[peasycap->input].saturation_ok = 1;
} else
JOM(8, "%i=peasycap->input\n", peasycap->input);
mood = 0x00FF & (unsigned int) (peasycap->saturation - 128);
if (write_saa(peasycap->pusb_device, 0x0C, mood)) {
SAM("WARNING: failed to adjust saturation to "
"0x%02X\n", mood);
return -ENOENT;
}
SAM("adjusting saturation to 0x%02X\n", mood);
return 0;
break;
}
i1++;
}
SAM("WARNING: failed to adjust saturation: control not found\n");
return -ENOENT;
}
/*****************************************************************************/
int adjust_hue(struct easycap *peasycap, int value)
{
unsigned int mood;
int i1, i2, k;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
i1 = 0;
while (0xFFFFFFFF != easycap_control[i1].id) {
if (V4L2_CID_HUE == easycap_control[i1].id) {
if ((easycap_control[i1].minimum > value) ||
(easycap_control[i1].maximum < value))
value = easycap_control[i1].default_value;
if ((easycap_control[i1].minimum <= peasycap->hue) &&
(easycap_control[i1].maximum >= peasycap->hue)) {
if (peasycap->hue == value) {
SAM("unchanged hue at 0x%02X\n", value);
return 0;
}
}
peasycap->hue = value;
for (k = 0; k < INPUT_MANY; k++) {
if (!peasycap->inputset[k].hue_ok)
peasycap->inputset[k].hue = peasycap->hue;
}
if (0 <= peasycap->input && INPUT_MANY > peasycap->input) {
peasycap->inputset[peasycap->input].hue = peasycap->hue;
peasycap->inputset[peasycap->input].hue_ok = 1;
} else
JOM(8, "%i=peasycap->input\n", peasycap->input);
i2 = peasycap->hue - 128;
mood = 0x00FF & ((int) i2);
if (write_saa(peasycap->pusb_device, 0x0D, mood)) {
SAM("WARNING: failed to adjust hue to 0x%02X\n", mood);
return -ENOENT;
}
SAM("adjusting hue to 0x%02X\n", mood);
return 0;
break;
}
i1++;
}
SAM("WARNING: failed to adjust hue: control not found\n");
return -ENOENT;
}
/*****************************************************************************/
static int adjust_volume(struct easycap *peasycap, int value)
{
s8 mood;
int i1;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
i1 = 0;
while (0xFFFFFFFF != easycap_control[i1].id) {
if (V4L2_CID_AUDIO_VOLUME == easycap_control[i1].id) {
if ((easycap_control[i1].minimum > value) ||
(easycap_control[i1].maximum < value))
value = easycap_control[i1].default_value;
if ((easycap_control[i1].minimum <= peasycap->volume) &&
(easycap_control[i1].maximum >= peasycap->volume)) {
if (peasycap->volume == value) {
SAM("unchanged volume at 0x%02X\n", value);
return 0;
}
}
peasycap->volume = value;
mood = (16 > peasycap->volume) ? 16 :
((31 < peasycap->volume) ? 31 :
(s8) peasycap->volume);
if (!easycap_audio_gainset(peasycap->pusb_device, mood)) {
SAM("WARNING: failed to adjust volume to "
"0x%2X\n", mood);
return -ENOENT;
}
SAM("adjusting volume to 0x%02X\n", mood);
return 0;
}
i1++;
}
SAM("WARNING: failed to adjust volume: control not found\n");
return -ENOENT;
}
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* AN ALTERNATIVE METHOD OF MUTING MIGHT SEEM TO BE:
* usb_set_interface(peasycap->pusb_device,
* peasycap->audio_interface,
* peasycap->audio_altsetting_off);
* HOWEVER, AFTER THIS COMMAND IS ISSUED ALL SUBSEQUENT URBS RECEIVE STATUS
* -ESHUTDOWN. THE HANDLER ROUTINE easyxxx_complete() DECLINES TO RESUBMIT
* THE URB AND THE PIPELINE COLLAPSES IRRETRIEVABLY. BEWARE.
*/
/*---------------------------------------------------------------------------*/
static int adjust_mute(struct easycap *peasycap, int value)
{
int i1;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
i1 = 0;
while (0xFFFFFFFF != easycap_control[i1].id) {
if (V4L2_CID_AUDIO_MUTE == easycap_control[i1].id) {
peasycap->mute = value;
switch (peasycap->mute) {
case 1: {
peasycap->audio_idle = 1;
SAM("adjusting mute: %i=peasycap->audio_idle\n",
peasycap->audio_idle);
return 0;
}
default: {
peasycap->audio_idle = 0;
SAM("adjusting mute: %i=peasycap->audio_idle\n",
peasycap->audio_idle);
return 0;
}
}
break;
}
i1++;
}
SAM("WARNING: failed to adjust mute: control not found\n");
return -ENOENT;
}
/*---------------------------------------------------------------------------*/
long easycap_unlocked_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct easycap *peasycap;
struct usb_device *p;
int kd;
if (!file) {
SAY("ERROR: file is NULL\n");
return -ERESTARTSYS;
}
peasycap = file->private_data;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -1;
}
p = peasycap->pusb_device;
if (!p) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
kd = easycap_isdongle(peasycap);
if (0 <= kd && DONGLE_MANY > kd) {
if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) {
SAY("ERROR: cannot lock "
"easycapdc60_dongle[%i].mutex_video\n", kd);
return -ERESTARTSYS;
}
JOM(4, "locked easycapdc60_dongle[%i].mutex_video\n", kd);
/*---------------------------------------------------------------------------*/
/*
* MEANWHILE, easycap_usb_disconnect() MAY HAVE FREED POINTER peasycap,
* IN WHICH CASE A REPEAT CALL TO isdongle() WILL FAIL.
* IF NECESSARY, BAIL OUT.
*/
/*---------------------------------------------------------------------------*/
if (kd != easycap_isdongle(peasycap))
return -ERESTARTSYS;
if (!file) {
SAY("ERROR: file is NULL\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ERESTARTSYS;
}
peasycap = file->private_data;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ERESTARTSYS;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ERESTARTSYS;
}
} else {
/*---------------------------------------------------------------------------*/
/*
* IF easycap_usb_disconnect() HAS ALREADY FREED POINTER peasycap BEFORE THE
* ATTEMPT TO ACQUIRE THE SEMAPHORE, isdongle() WILL HAVE FAILED. BAIL OUT.
*/
/*---------------------------------------------------------------------------*/
return -ERESTARTSYS;
}
/*---------------------------------------------------------------------------*/
switch (cmd) {
case VIDIOC_QUERYCAP: {
struct v4l2_capability v4l2_capability;
char version[16], *p1, *p2;
int i, rc, k[3];
long lng;
JOM(8, "VIDIOC_QUERYCAP\n");
if (16 <= strlen(EASYCAP_DRIVER_VERSION)) {
SAM("ERROR: bad driver version string\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
strcpy(&version[0], EASYCAP_DRIVER_VERSION);
for (i = 0; i < 3; i++)
k[i] = 0;
p2 = &version[0];
i = 0;
while (*p2) {
p1 = p2;
while (*p2 && ('.' != *p2))
p2++;
if (*p2)
*p2++ = 0;
if (3 > i) {
rc = (int) strict_strtol(p1, 10, &lng);
if (rc) {
SAM("ERROR: %i=strict_strtol(%s,.,,)\n",
rc, p1);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
k[i] = (int)lng;
}
i++;
}
memset(&v4l2_capability, 0, sizeof(struct v4l2_capability));
strlcpy(&v4l2_capability.driver[0],
"easycap", sizeof(v4l2_capability.driver));
v4l2_capability.capabilities = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_STREAMING |
V4L2_CAP_AUDIO |
V4L2_CAP_READWRITE;
v4l2_capability.version = KERNEL_VERSION(k[0], k[1], k[2]);
JOM(8, "v4l2_capability.version=(%i,%i,%i)\n", k[0], k[1], k[2]);
strlcpy(&v4l2_capability.card[0],
"EasyCAP DC60", sizeof(v4l2_capability.card));
if (usb_make_path(peasycap->pusb_device,
&v4l2_capability.bus_info[0],
sizeof(v4l2_capability.bus_info)) < 0) {
strlcpy(&v4l2_capability.bus_info[0], "EasyCAP bus_info",
sizeof(v4l2_capability.bus_info));
JOM(8, "%s=v4l2_capability.bus_info\n",
&v4l2_capability.bus_info[0]);
}
if (copy_to_user((void __user *)arg, &v4l2_capability,
sizeof(struct v4l2_capability))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_ENUMINPUT: {
struct v4l2_input v4l2_input;
u32 index;
JOM(8, "VIDIOC_ENUMINPUT\n");
if (copy_from_user(&v4l2_input, (void __user *)arg,
sizeof(struct v4l2_input))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
index = v4l2_input.index;
memset(&v4l2_input, 0, sizeof(struct v4l2_input));
switch (index) {
case 0: {
v4l2_input.index = index;
strcpy(&v4l2_input.name[0], "CVBS0");
v4l2_input.type = V4L2_INPUT_TYPE_CAMERA;
v4l2_input.audioset = 0x01;
v4l2_input.tuner = 0;
v4l2_input.std = V4L2_STD_PAL |
V4L2_STD_SECAM |
V4L2_STD_NTSC ;
v4l2_input.status = 0;
JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]);
break;
}
case 1: {
v4l2_input.index = index;
strcpy(&v4l2_input.name[0], "CVBS1");
v4l2_input.type = V4L2_INPUT_TYPE_CAMERA;
v4l2_input.audioset = 0x01;
v4l2_input.tuner = 0;
v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM |
V4L2_STD_NTSC;
v4l2_input.status = 0;
JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]);
break;
}
case 2: {
v4l2_input.index = index;
strcpy(&v4l2_input.name[0], "CVBS2");
v4l2_input.type = V4L2_INPUT_TYPE_CAMERA;
v4l2_input.audioset = 0x01;
v4l2_input.tuner = 0;
v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM |
V4L2_STD_NTSC ;
v4l2_input.status = 0;
JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]);
break;
}
case 3: {
v4l2_input.index = index;
strcpy(&v4l2_input.name[0], "CVBS3");
v4l2_input.type = V4L2_INPUT_TYPE_CAMERA;
v4l2_input.audioset = 0x01;
v4l2_input.tuner = 0;
v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM |
V4L2_STD_NTSC ;
v4l2_input.status = 0;
JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]);
break;
}
case 4: {
v4l2_input.index = index;
strcpy(&v4l2_input.name[0], "CVBS4");
v4l2_input.type = V4L2_INPUT_TYPE_CAMERA;
v4l2_input.audioset = 0x01;
v4l2_input.tuner = 0;
v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM |
V4L2_STD_NTSC ;
v4l2_input.status = 0;
JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]);
break;
}
case 5: {
v4l2_input.index = index;
strcpy(&v4l2_input.name[0], "S-VIDEO");
v4l2_input.type = V4L2_INPUT_TYPE_CAMERA;
v4l2_input.audioset = 0x01;
v4l2_input.tuner = 0;
v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM |
V4L2_STD_NTSC ;
v4l2_input.status = 0;
JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]);
break;
}
default: {
JOM(8, "%i=index: exhausts inputs\n", index);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
}
if (copy_to_user((void __user *)arg, &v4l2_input,
sizeof(struct v4l2_input))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_INPUT: {
u32 index;
JOM(8, "VIDIOC_G_INPUT\n");
index = (u32)peasycap->input;
JOM(8, "user is told: %i\n", index);
if (copy_to_user((void __user *)arg, &index, sizeof(u32))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_S_INPUT:
{
u32 index;
int rc;
JOM(8, "VIDIOC_S_INPUT\n");
if (0 != copy_from_user(&index, (void __user *)arg, sizeof(u32))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
JOM(8, "user requests input %i\n", index);
if ((int)index == peasycap->input) {
SAM("requested input already in effect\n");
break;
}
if ((0 > index) || (INPUT_MANY <= index)) {
JOM(8, "ERROR: bad requested input: %i\n", index);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
rc = easycap_newinput(peasycap, (int)index);
if (0 == rc) {
JOM(8, "newinput(.,%i) OK\n", (int)index);
} else {
SAM("ERROR: newinput(.,%i) returned %i\n", (int)index, rc);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_ENUMAUDIO: {
JOM(8, "VIDIOC_ENUMAUDIO\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_ENUMAUDOUT: {
struct v4l2_audioout v4l2_audioout;
JOM(8, "VIDIOC_ENUMAUDOUT\n");
if (copy_from_user(&v4l2_audioout, (void __user *)arg,
sizeof(struct v4l2_audioout))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
if (0 != v4l2_audioout.index) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
memset(&v4l2_audioout, 0, sizeof(struct v4l2_audioout));
v4l2_audioout.index = 0;
strcpy(&v4l2_audioout.name[0], "Soundtrack");
if (copy_to_user((void __user *)arg, &v4l2_audioout,
sizeof(struct v4l2_audioout))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_QUERYCTRL: {
int i1;
struct v4l2_queryctrl v4l2_queryctrl;
JOM(8, "VIDIOC_QUERYCTRL\n");
if (0 != copy_from_user(&v4l2_queryctrl, (void __user *)arg,
sizeof(struct v4l2_queryctrl))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
i1 = 0;
while (0xFFFFFFFF != easycap_control[i1].id) {
if (easycap_control[i1].id == v4l2_queryctrl.id) {
JOM(8, "VIDIOC_QUERYCTRL %s=easycap_control[%i]"
".name\n", &easycap_control[i1].name[0], i1);
memcpy(&v4l2_queryctrl, &easycap_control[i1],
sizeof(struct v4l2_queryctrl));
break;
}
i1++;
}
if (0xFFFFFFFF == easycap_control[i1].id) {
JOM(8, "%i=index: exhausts controls\n", i1);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
if (copy_to_user((void __user *)arg, &v4l2_queryctrl,
sizeof(struct v4l2_queryctrl))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_QUERYMENU: {
JOM(8, "VIDIOC_QUERYMENU unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_CTRL: {
struct v4l2_control *pv4l2_control;
JOM(8, "VIDIOC_G_CTRL\n");
pv4l2_control = memdup_user((void __user *)arg,
sizeof(struct v4l2_control));
if (IS_ERR(pv4l2_control)) {
SAM("ERROR: copy from user failed\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return PTR_ERR(pv4l2_control);
}
switch (pv4l2_control->id) {
case V4L2_CID_BRIGHTNESS: {
pv4l2_control->value = peasycap->brightness;
JOM(8, "user enquires brightness: %i\n", pv4l2_control->value);
break;
}
case V4L2_CID_CONTRAST: {
pv4l2_control->value = peasycap->contrast;
JOM(8, "user enquires contrast: %i\n", pv4l2_control->value);
break;
}
case V4L2_CID_SATURATION: {
pv4l2_control->value = peasycap->saturation;
JOM(8, "user enquires saturation: %i\n", pv4l2_control->value);
break;
}
case V4L2_CID_HUE: {
pv4l2_control->value = peasycap->hue;
JOM(8, "user enquires hue: %i\n", pv4l2_control->value);
break;
}
case V4L2_CID_AUDIO_VOLUME: {
pv4l2_control->value = peasycap->volume;
JOM(8, "user enquires volume: %i\n", pv4l2_control->value);
break;
}
case V4L2_CID_AUDIO_MUTE: {
if (1 == peasycap->mute)
pv4l2_control->value = true;
else
pv4l2_control->value = false;
JOM(8, "user enquires mute: %i\n", pv4l2_control->value);
break;
}
default: {
SAM("ERROR: unknown V4L2 control: 0x%08X=id\n",
pv4l2_control->id);
kfree(pv4l2_control);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
}
if (copy_to_user((void __user *)arg, pv4l2_control,
sizeof(struct v4l2_control))) {
kfree(pv4l2_control);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
kfree(pv4l2_control);
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_S_CTRL: {
struct v4l2_control v4l2_control;
JOM(8, "VIDIOC_S_CTRL\n");
if (0 != copy_from_user(&v4l2_control, (void __user *)arg,
sizeof(struct v4l2_control))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
switch (v4l2_control.id) {
case V4L2_CID_BRIGHTNESS: {
JOM(8, "user requests brightness %i\n", v4l2_control.value);
if (0 != adjust_brightness(peasycap, v4l2_control.value))
;
break;
}
case V4L2_CID_CONTRAST: {
JOM(8, "user requests contrast %i\n", v4l2_control.value);
if (0 != adjust_contrast(peasycap, v4l2_control.value))
;
break;
}
case V4L2_CID_SATURATION: {
JOM(8, "user requests saturation %i\n", v4l2_control.value);
if (0 != adjust_saturation(peasycap, v4l2_control.value))
;
break;
}
case V4L2_CID_HUE: {
JOM(8, "user requests hue %i\n", v4l2_control.value);
if (0 != adjust_hue(peasycap, v4l2_control.value))
;
break;
}
case V4L2_CID_AUDIO_VOLUME: {
JOM(8, "user requests volume %i\n", v4l2_control.value);
if (0 != adjust_volume(peasycap, v4l2_control.value))
;
break;
}
case V4L2_CID_AUDIO_MUTE: {
int mute;
JOM(8, "user requests mute %i\n", v4l2_control.value);
if (v4l2_control.value)
mute = 1;
else
mute = 0;
if (0 != adjust_mute(peasycap, mute))
SAM("WARNING: failed to adjust mute to %i\n", mute);
break;
}
default: {
SAM("ERROR: unknown V4L2 control: 0x%08X=id\n",
v4l2_control.id);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_S_EXT_CTRLS: {
JOM(8, "VIDIOC_S_EXT_CTRLS unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_ENUM_FMT: {
u32 index;
struct v4l2_fmtdesc v4l2_fmtdesc;
JOM(8, "VIDIOC_ENUM_FMT\n");
if (0 != copy_from_user(&v4l2_fmtdesc, (void __user *)arg,
sizeof(struct v4l2_fmtdesc))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
index = v4l2_fmtdesc.index;
memset(&v4l2_fmtdesc, 0, sizeof(struct v4l2_fmtdesc));
v4l2_fmtdesc.index = index;
v4l2_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
switch (index) {
case 0: {
v4l2_fmtdesc.flags = 0;
strcpy(&v4l2_fmtdesc.description[0], "uyvy");
v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_UYVY;
JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]);
break;
}
case 1: {
v4l2_fmtdesc.flags = 0;
strcpy(&v4l2_fmtdesc.description[0], "yuy2");
v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_YUYV;
JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]);
break;
}
case 2: {
v4l2_fmtdesc.flags = 0;
strcpy(&v4l2_fmtdesc.description[0], "rgb24");
v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_RGB24;
JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]);
break;
}
case 3: {
v4l2_fmtdesc.flags = 0;
strcpy(&v4l2_fmtdesc.description[0], "rgb32");
v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_RGB32;
JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]);
break;
}
case 4: {
v4l2_fmtdesc.flags = 0;
strcpy(&v4l2_fmtdesc.description[0], "bgr24");
v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_BGR24;
JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]);
break;
}
case 5: {
v4l2_fmtdesc.flags = 0;
strcpy(&v4l2_fmtdesc.description[0], "bgr32");
v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_BGR32;
JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]);
break;
}
default: {
JOM(8, "%i=index: exhausts formats\n", index);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
}
if (copy_to_user((void __user *)arg, &v4l2_fmtdesc,
sizeof(struct v4l2_fmtdesc))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* THE RESPONSE TO VIDIOC_ENUM_FRAMESIZES MUST BE CONDITIONED ON THE
* THE CURRENT STANDARD, BECAUSE THAT IS WHAT gstreamer EXPECTS. BEWARE.
*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_ENUM_FRAMESIZES: {
u32 index;
struct v4l2_frmsizeenum v4l2_frmsizeenum;
JOM(8, "VIDIOC_ENUM_FRAMESIZES\n");
if (0 != copy_from_user(&v4l2_frmsizeenum, (void __user *)arg,
sizeof(struct v4l2_frmsizeenum))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
index = v4l2_frmsizeenum.index;
v4l2_frmsizeenum.type = (u32) V4L2_FRMSIZE_TYPE_DISCRETE;
if (peasycap->ntsc) {
switch (index) {
case 0: {
v4l2_frmsizeenum.discrete.width = 640;
v4l2_frmsizeenum.discrete.height = 480;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
case 1: {
v4l2_frmsizeenum.discrete.width = 320;
v4l2_frmsizeenum.discrete.height = 240;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
case 2: {
v4l2_frmsizeenum.discrete.width = 720;
v4l2_frmsizeenum.discrete.height = 480;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
case 3: {
v4l2_frmsizeenum.discrete.width = 360;
v4l2_frmsizeenum.discrete.height = 240;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
default: {
JOM(8, "%i=index: exhausts framesizes\n", index);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
}
} else {
switch (index) {
case 0: {
v4l2_frmsizeenum.discrete.width = 640;
v4l2_frmsizeenum.discrete.height = 480;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
case 1: {
v4l2_frmsizeenum.discrete.width = 320;
v4l2_frmsizeenum.discrete.height = 240;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
case 2: {
v4l2_frmsizeenum.discrete.width = 704;
v4l2_frmsizeenum.discrete.height = 576;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
case 3: {
v4l2_frmsizeenum.discrete.width = 720;
v4l2_frmsizeenum.discrete.height = 576;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
case 4: {
v4l2_frmsizeenum.discrete.width = 360;
v4l2_frmsizeenum.discrete.height = 288;
JOM(8, "%i=index: %ix%i\n", index,
(int)(v4l2_frmsizeenum.
discrete.width),
(int)(v4l2_frmsizeenum.
discrete.height));
break;
}
default: {
JOM(8, "%i=index: exhausts framesizes\n", index);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
}
}
if (copy_to_user((void __user *)arg, &v4l2_frmsizeenum,
sizeof(struct v4l2_frmsizeenum))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* THE RESPONSE TO VIDIOC_ENUM_FRAMEINTERVALS MUST BE CONDITIONED ON THE
* THE CURRENT STANDARD, BECAUSE THAT IS WHAT gstreamer EXPECTS. BEWARE.
*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_ENUM_FRAMEINTERVALS: {
u32 index;
int denominator;
struct v4l2_frmivalenum v4l2_frmivalenum;
JOM(8, "VIDIOC_ENUM_FRAMEINTERVALS\n");
if (peasycap->fps)
denominator = peasycap->fps;
else {
if (peasycap->ntsc)
denominator = 30;
else
denominator = 25;
}
if (0 != copy_from_user(&v4l2_frmivalenum, (void __user *)arg,
sizeof(struct v4l2_frmivalenum))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
index = v4l2_frmivalenum.index;
v4l2_frmivalenum.type = (u32) V4L2_FRMIVAL_TYPE_DISCRETE;
switch (index) {
case 0: {
v4l2_frmivalenum.discrete.numerator = 1;
v4l2_frmivalenum.discrete.denominator = denominator;
JOM(8, "%i=index: %i/%i\n", index,
(int)(v4l2_frmivalenum.discrete.numerator),
(int)(v4l2_frmivalenum.discrete.denominator));
break;
}
case 1: {
v4l2_frmivalenum.discrete.numerator = 1;
v4l2_frmivalenum.discrete.denominator = denominator/5;
JOM(8, "%i=index: %i/%i\n", index,
(int)(v4l2_frmivalenum.discrete.numerator),
(int)(v4l2_frmivalenum.discrete.denominator));
break;
}
default: {
JOM(8, "%i=index: exhausts frameintervals\n", index);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
}
if (copy_to_user((void __user *)arg, &v4l2_frmivalenum,
sizeof(struct v4l2_frmivalenum))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_FMT: {
struct v4l2_format *pv4l2_format;
struct v4l2_pix_format *pv4l2_pix_format;
JOM(8, "VIDIOC_G_FMT\n");
pv4l2_format = kzalloc(sizeof(struct v4l2_format), GFP_KERNEL);
if (!pv4l2_format) {
SAM("ERROR: out of memory\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ENOMEM;
}
pv4l2_pix_format = kzalloc(sizeof(struct v4l2_pix_format), GFP_KERNEL);
if (!pv4l2_pix_format) {
SAM("ERROR: out of memory\n");
kfree(pv4l2_format);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ENOMEM;
}
if (0 != copy_from_user(pv4l2_format, (void __user *)arg,
sizeof(struct v4l2_format))) {
kfree(pv4l2_format);
kfree(pv4l2_pix_format);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
if (pv4l2_format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
kfree(pv4l2_format);
kfree(pv4l2_pix_format);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
memset(pv4l2_pix_format, 0, sizeof(struct v4l2_pix_format));
pv4l2_format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
memcpy(&pv4l2_format->fmt.pix,
&easycap_format[peasycap->format_offset]
.v4l2_format.fmt.pix, sizeof(struct v4l2_pix_format));
JOM(8, "user is told: %s\n",
&easycap_format[peasycap->format_offset].name[0]);
if (copy_to_user((void __user *)arg, pv4l2_format,
sizeof(struct v4l2_format))) {
kfree(pv4l2_format);
kfree(pv4l2_pix_format);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
kfree(pv4l2_format);
kfree(pv4l2_pix_format);
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_TRY_FMT:
case VIDIOC_S_FMT: {
struct v4l2_format v4l2_format;
struct v4l2_pix_format v4l2_pix_format;
bool try;
int best_format;
if (VIDIOC_TRY_FMT == cmd) {
JOM(8, "VIDIOC_TRY_FMT\n");
try = true;
} else {
JOM(8, "VIDIOC_S_FMT\n");
try = false;
}
if (0 != copy_from_user(&v4l2_format, (void __user *)arg,
sizeof(struct v4l2_format))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
best_format = adjust_format(peasycap,
v4l2_format.fmt.pix.width,
v4l2_format.fmt.pix.height,
v4l2_format.fmt.pix.pixelformat,
v4l2_format.fmt.pix.field,
try);
if (0 > best_format) {
if (-EBUSY == best_format) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EBUSY;
}
JOM(8, "WARNING: adjust_format() returned %i\n", best_format);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ENOENT;
}
/*...........................................................................*/
memset(&v4l2_pix_format, 0, sizeof(struct v4l2_pix_format));
v4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
memcpy(&(v4l2_format.fmt.pix),
&(easycap_format[best_format].v4l2_format.fmt.pix),
sizeof(v4l2_pix_format));
JOM(8, "user is told: %s\n", &easycap_format[best_format].name[0]);
if (copy_to_user((void __user *)arg, &v4l2_format,
sizeof(struct v4l2_format))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_CROPCAP: {
struct v4l2_cropcap v4l2_cropcap;
JOM(8, "VIDIOC_CROPCAP\n");
if (0 != copy_from_user(&v4l2_cropcap, (void __user *)arg,
sizeof(struct v4l2_cropcap))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
if (v4l2_cropcap.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
JOM(8, "v4l2_cropcap.type != V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
memset(&v4l2_cropcap, 0, sizeof(struct v4l2_cropcap));
v4l2_cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_cropcap.bounds.left = 0;
v4l2_cropcap.bounds.top = 0;
v4l2_cropcap.bounds.width = peasycap->width;
v4l2_cropcap.bounds.height = peasycap->height;
v4l2_cropcap.defrect.left = 0;
v4l2_cropcap.defrect.top = 0;
v4l2_cropcap.defrect.width = peasycap->width;
v4l2_cropcap.defrect.height = peasycap->height;
v4l2_cropcap.pixelaspect.numerator = 1;
v4l2_cropcap.pixelaspect.denominator = 1;
JOM(8, "user is told: %ix%i\n", peasycap->width, peasycap->height);
if (copy_to_user((void __user *)arg, &v4l2_cropcap,
sizeof(struct v4l2_cropcap))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_CROP:
case VIDIOC_S_CROP: {
JOM(8, "VIDIOC_G_CROP|VIDIOC_S_CROP unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_QUERYSTD: {
JOM(8, "VIDIOC_QUERYSTD: "
"EasyCAP is incapable of detecting standard\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
break;
}
/*-------------------------------------------------------------------*/
/*
* THE MANIPULATIONS INVOLVING last0,last1,last2,last3
* CONSTITUTE A WORKAROUND * FOR WHAT APPEARS TO BE
* A BUG IN 64-BIT mplayer.
* NOT NEEDED, BUT HOPEFULLY HARMLESS, FOR 32-BIT mplayer.
*/
/*------------------------------------------------------------------*/
case VIDIOC_ENUMSTD: {
int last0 = -1, last1 = -1, last2 = -1, last3 = -1;
struct v4l2_standard v4l2_standard;
u32 index;
struct easycap_standard const *peasycap_standard;
JOM(8, "VIDIOC_ENUMSTD\n");
if (0 != copy_from_user(&v4l2_standard, (void __user *)arg,
sizeof(struct v4l2_standard))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
index = v4l2_standard.index;
last3 = last2;
last2 = last1;
last1 = last0;
last0 = index;
if ((index == last3) && (index == last2) &&
(index == last1) && (index == last0)) {
index++;
last3 = last2;
last2 = last1;
last1 = last0;
last0 = index;
}
memset(&v4l2_standard, 0, sizeof(struct v4l2_standard));
peasycap_standard = &easycap_standard[0];
while (0xFFFF != peasycap_standard->mask) {
if ((int)(peasycap_standard - &easycap_standard[0]) == index)
break;
peasycap_standard++;
}
if (0xFFFF == peasycap_standard->mask) {
JOM(8, "%i=index: exhausts standards\n", index);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
JOM(8, "%i=index: %s\n", index,
&(peasycap_standard->v4l2_standard.name[0]));
memcpy(&v4l2_standard, &(peasycap_standard->v4l2_standard),
sizeof(struct v4l2_standard));
v4l2_standard.index = index;
if (copy_to_user((void __user *)arg, &v4l2_standard,
sizeof(struct v4l2_standard))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_STD: {
v4l2_std_id std_id;
struct easycap_standard const *peasycap_standard;
JOM(8, "VIDIOC_G_STD\n");
if (0 > peasycap->standard_offset) {
JOM(8, "%i=peasycap->standard_offset\n",
peasycap->standard_offset);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EBUSY;
}
if (0 != copy_from_user(&std_id, (void __user *)arg,
sizeof(v4l2_std_id))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
peasycap_standard = &easycap_standard[peasycap->standard_offset];
std_id = peasycap_standard->v4l2_standard.id;
JOM(8, "user is told: %s\n",
&peasycap_standard->v4l2_standard.name[0]);
if (copy_to_user((void __user *)arg, &std_id,
sizeof(v4l2_std_id))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_S_STD: {
v4l2_std_id std_id;
int rc;
JOM(8, "VIDIOC_S_STD\n");
if (0 != copy_from_user(&std_id, (void __user *)arg,
sizeof(v4l2_std_id))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
JOM(8, "User requests standard: 0x%08X%08X\n",
(int)((std_id & (((v4l2_std_id)0xFFFFFFFF) << 32)) >> 32),
(int)(std_id & ((v4l2_std_id)0xFFFFFFFF)));
rc = adjust_standard(peasycap, std_id);
if (0 > rc) {
JOM(8, "WARNING: adjust_standard() returned %i\n", rc);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ENOENT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_REQBUFS: {
int nbuffers;
struct v4l2_requestbuffers v4l2_requestbuffers;
JOM(8, "VIDIOC_REQBUFS\n");
if (0 != copy_from_user(&v4l2_requestbuffers,
(void __user *)arg,
sizeof(struct v4l2_requestbuffers))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
if (v4l2_requestbuffers.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
if (v4l2_requestbuffers.memory != V4L2_MEMORY_MMAP) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
nbuffers = v4l2_requestbuffers.count;
JOM(8, " User requests %i buffers ...\n", nbuffers);
if (nbuffers < 2)
nbuffers = 2;
if (nbuffers > FRAME_BUFFER_MANY)
nbuffers = FRAME_BUFFER_MANY;
if (v4l2_requestbuffers.count == nbuffers) {
JOM(8, " ... agree to %i buffers\n",
nbuffers);
} else {
JOM(8, " ... insist on %i buffers\n",
nbuffers);
v4l2_requestbuffers.count = nbuffers;
}
peasycap->frame_buffer_many = nbuffers;
if (copy_to_user((void __user *)arg, &v4l2_requestbuffers,
sizeof(struct v4l2_requestbuffers))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_QUERYBUF: {
u32 index;
struct v4l2_buffer v4l2_buffer;
JOM(8, "VIDIOC_QUERYBUF\n");
if (peasycap->video_eof) {
JOM(8, "returning -EIO because %i=video_eof\n",
peasycap->video_eof);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EIO;
}
if (0 != copy_from_user(&v4l2_buffer, (void __user *)arg,
sizeof(struct v4l2_buffer))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
index = v4l2_buffer.index;
if (index < 0 || index >= peasycap->frame_buffer_many)
return -EINVAL;
memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
v4l2_buffer.index = index;
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.bytesused = peasycap->frame_buffer_used;
v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED |
peasycap->done[index] |
peasycap->queued[index];
v4l2_buffer.field = V4L2_FIELD_NONE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;
v4l2_buffer.m.offset = index * FRAME_BUFFER_SIZE;
v4l2_buffer.length = FRAME_BUFFER_SIZE;
JOM(16, " %10i=index\n", v4l2_buffer.index);
JOM(16, " 0x%08X=type\n", v4l2_buffer.type);
JOM(16, " %10i=bytesused\n", v4l2_buffer.bytesused);
JOM(16, " 0x%08X=flags\n", v4l2_buffer.flags);
JOM(16, " %10i=field\n", v4l2_buffer.field);
JOM(16, " %10li=timestamp.tv_usec\n",
(long)v4l2_buffer.timestamp.tv_usec);
JOM(16, " %10i=sequence\n", v4l2_buffer.sequence);
JOM(16, " 0x%08X=memory\n", v4l2_buffer.memory);
JOM(16, " %10i=m.offset\n", v4l2_buffer.m.offset);
JOM(16, " %10i=length\n", v4l2_buffer.length);
if (copy_to_user((void __user *)arg, &v4l2_buffer,
sizeof(struct v4l2_buffer))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_QBUF: {
struct v4l2_buffer v4l2_buffer;
JOM(8, "VIDIOC_QBUF\n");
if (0 != copy_from_user(&v4l2_buffer, (void __user *)arg,
sizeof(struct v4l2_buffer))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
if (v4l2_buffer.memory != V4L2_MEMORY_MMAP) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
if (v4l2_buffer.index < 0 ||
v4l2_buffer.index >= peasycap->frame_buffer_many) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED;
peasycap->done[v4l2_buffer.index] = 0;
peasycap->queued[v4l2_buffer.index] = V4L2_BUF_FLAG_QUEUED;
if (copy_to_user((void __user *)arg, &v4l2_buffer,
sizeof(struct v4l2_buffer))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
JOM(8, "..... user queueing frame buffer %i\n",
(int)v4l2_buffer.index);
peasycap->frame_lock = 0;
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_DQBUF:
{
struct timeval timeval, timeval2;
int i, j;
struct v4l2_buffer v4l2_buffer;
int rcdq;
u16 input;
JOM(8, "VIDIOC_DQBUF\n");
if ((peasycap->video_idle) || (peasycap->video_eof)) {
JOM(8, "returning -EIO because "
"%i=video_idle %i=video_eof\n",
peasycap->video_idle, peasycap->video_eof);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EIO;
}
if (copy_from_user(&v4l2_buffer, (void __user *)arg,
sizeof(struct v4l2_buffer))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
if (peasycap->offerfields) {
/*---------------------------------------------------*/
/*
* IN ITS 50 "fps" MODE tvtime SEEMS ALWAYS TO REQUEST
* V4L2_FIELD_BOTTOM
*/
/*---------------------------------------------------*/
if (V4L2_FIELD_TOP == v4l2_buffer.field)
JOM(8, "user wants V4L2_FIELD_TOP\n");
else if (V4L2_FIELD_BOTTOM == v4l2_buffer.field)
JOM(8, "user wants V4L2_FIELD_BOTTOM\n");
else if (V4L2_FIELD_ANY == v4l2_buffer.field)
JOM(8, "user wants V4L2_FIELD_ANY\n");
else
JOM(8, "user wants V4L2_FIELD_...UNKNOWN: %i\n",
v4l2_buffer.field);
}
if (!peasycap->video_isoc_streaming) {
JOM(16, "returning -EIO because video urbs not streaming\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EIO;
}
/*-------------------------------------------------------------------*/
/*
* IF THE USER HAS PREVIOUSLY CALLED easycap_poll(),
* AS DETERMINED BY FINDING
* THE FLAG peasycap->polled SET, THERE MUST BE
* NO FURTHER WAIT HERE. IN THIS
* CASE, JUST CHOOSE THE FRAME INDICATED BY peasycap->frame_read
*/
/*-------------------------------------------------------------------*/
if (!peasycap->polled) {
do {
rcdq = easycap_video_dqbuf(peasycap, 0);
if (-EIO == rcdq) {
JOM(8, "returning -EIO because "
"dqbuf() returned -EIO\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EIO;
}
} while (0 != rcdq);
} else {
if (peasycap->video_eof) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EIO;
}
}
if (V4L2_BUF_FLAG_DONE != peasycap->done[peasycap->frame_read]) {
JOM(8, "V4L2_BUF_FLAG_DONE != 0x%08X\n",
peasycap->done[peasycap->frame_read]);
}
peasycap->polled = 0;
if (!(peasycap->isequence % 10)) {
for (i = 0; i < 179; i++)
peasycap->merit[i] = peasycap->merit[i+1];
peasycap->merit[179] = merit_saa(peasycap->pusb_device);
j = 0;
for (i = 0; i < 180; i++)
j += peasycap->merit[i];
if (90 < j) {
SAM("easycap driver shutting down "
"on condition blue\n");
peasycap->video_eof = 1;
peasycap->audio_eof = 1;
}
}
v4l2_buffer.index = peasycap->frame_read;
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.bytesused = peasycap->frame_buffer_used;
v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE;
if (peasycap->offerfields)
v4l2_buffer.field = V4L2_FIELD_BOTTOM;
else
v4l2_buffer.field = V4L2_FIELD_NONE;
do_gettimeofday(&timeval);
timeval2 = timeval;
v4l2_buffer.timestamp = timeval2;
v4l2_buffer.sequence = peasycap->isequence++;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;
v4l2_buffer.m.offset = v4l2_buffer.index * FRAME_BUFFER_SIZE;
v4l2_buffer.length = FRAME_BUFFER_SIZE;
JOM(16, " %10i=index\n", v4l2_buffer.index);
JOM(16, " 0x%08X=type\n", v4l2_buffer.type);
JOM(16, " %10i=bytesused\n", v4l2_buffer.bytesused);
JOM(16, " 0x%08X=flags\n", v4l2_buffer.flags);
JOM(16, " %10i=field\n", v4l2_buffer.field);
JOM(16, " %10li=timestamp.tv_sec\n",
(long)v4l2_buffer.timestamp.tv_sec);
JOM(16, " %10li=timestamp.tv_usec\n",
(long)v4l2_buffer.timestamp.tv_usec);
JOM(16, " %10i=sequence\n", v4l2_buffer.sequence);
JOM(16, " 0x%08X=memory\n", v4l2_buffer.memory);
JOM(16, " %10i=m.offset\n", v4l2_buffer.m.offset);
JOM(16, " %10i=length\n", v4l2_buffer.length);
if (copy_to_user((void __user *)arg, &v4l2_buffer,
sizeof(struct v4l2_buffer))) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
input = peasycap->frame_buffer[peasycap->frame_read][0].input;
if (0x08 & input) {
JOM(8, "user is offered frame buffer %i, input %i\n",
peasycap->frame_read, (0x07 & input));
} else {
JOM(8, "user is offered frame buffer %i\n",
peasycap->frame_read);
}
peasycap->frame_lock = 1;
JOM(8, "%i=peasycap->frame_fill\n", peasycap->frame_fill);
if (peasycap->frame_read == peasycap->frame_fill) {
if (peasycap->frame_lock) {
JOM(8, "WORRY: filling frame buffer "
"while offered to user\n");
}
}
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_STREAMON: {
int i;
JOM(8, "VIDIOC_STREAMON\n");
peasycap->isequence = 0;
for (i = 0; i < 180; i++)
peasycap->merit[i] = 0;
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
easycap_video_submit_urbs(peasycap);
peasycap->video_idle = 0;
peasycap->audio_idle = 0;
peasycap->video_eof = 0;
peasycap->audio_eof = 0;
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_STREAMOFF: {
JOM(8, "VIDIOC_STREAMOFF\n");
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
peasycap->video_idle = 1;
peasycap->audio_idle = 1;
/*---------------------------------------------------------------------------*/
/*
* IF THE WAIT QUEUES ARE NOT CLEARED IN RESPONSE TO THE STREAMOFF COMMAND
* THE USERSPACE PROGRAM, E.G. mplayer, MAY HANG ON EXIT. BEWARE.
*/
/*---------------------------------------------------------------------------*/
JOM(8, "calling wake_up on wq_video and wq_audio\n");
wake_up_interruptible(&(peasycap->wq_video));
if (peasycap->psubstream)
snd_pcm_period_elapsed(peasycap->psubstream);
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_PARM: {
struct v4l2_streamparm *pv4l2_streamparm;
JOM(8, "VIDIOC_G_PARM\n");
pv4l2_streamparm = memdup_user((void __user *)arg,
sizeof(struct v4l2_streamparm));
if (IS_ERR(pv4l2_streamparm)) {
SAM("ERROR: copy from user failed\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return PTR_ERR(pv4l2_streamparm);
}
if (pv4l2_streamparm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
kfree(pv4l2_streamparm);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
pv4l2_streamparm->parm.capture.capability = 0;
pv4l2_streamparm->parm.capture.capturemode = 0;
pv4l2_streamparm->parm.capture.timeperframe.numerator = 1;
if (peasycap->fps) {
pv4l2_streamparm->parm.capture.timeperframe.
denominator = peasycap->fps;
} else {
if (peasycap->ntsc) {
pv4l2_streamparm->parm.capture.timeperframe.
denominator = 30;
} else {
pv4l2_streamparm->parm.capture.timeperframe.
denominator = 25;
}
}
pv4l2_streamparm->parm.capture.readbuffers =
peasycap->frame_buffer_many;
pv4l2_streamparm->parm.capture.extendedmode = 0;
if (copy_to_user((void __user *)arg,
pv4l2_streamparm,
sizeof(struct v4l2_streamparm))) {
kfree(pv4l2_streamparm);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EFAULT;
}
kfree(pv4l2_streamparm);
break;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_S_PARM: {
JOM(8, "VIDIOC_S_PARM unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_AUDIO: {
JOM(8, "VIDIOC_G_AUDIO unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_S_AUDIO: {
JOM(8, "VIDIOC_S_AUDIO unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_S_TUNER: {
JOM(8, "VIDIOC_S_TUNER unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_FBUF:
case VIDIOC_S_FBUF:
case VIDIOC_OVERLAY: {
JOM(8, "VIDIOC_G_FBUF|VIDIOC_S_FBUF|VIDIOC_OVERLAY unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case VIDIOC_G_TUNER: {
JOM(8, "VIDIOC_G_TUNER unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
case VIDIOC_G_FREQUENCY:
case VIDIOC_S_FREQUENCY: {
JOM(8, "VIDIOC_G_FREQUENCY|VIDIOC_S_FREQUENCY unsupported\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -EINVAL;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
default: {
JOM(8, "ERROR: unrecognized V4L2 IOCTL command: 0x%08X\n", cmd);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ENOIOCTLCMD;
}
}
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
JOM(4, "unlocked easycapdc60_dongle[%i].mutex_video\n", kd);
return 0;
}
/*****************************************************************************/
/*****************************************************************************
* *
* *
* easycap_low.c *
* *
* *
*****************************************************************************/
/*
*
* Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org>
*
*
* This 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, or
* (at your option) any later version.
*
* The software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*****************************************************************************/
/*
* ACKNOWLEGEMENTS AND REFERENCES
* ------------------------------
* This driver makes use of register information contained in the Syntek
* Semicon DC-1125 driver hosted at
* http://sourceforge.net/projects/syntekdriver/.
* Particularly useful has been a patch to the latter driver provided by
* Ivor Hewitt in January 2009. The NTSC implementation is taken from the
* work of Ben Trask.
*/
/****************************************************************************/
#include "easycap.h"
#define GET(X, Y, Z) do { \
int __rc; \
*(Z) = (u16)0; \
__rc = regget(X, Y, Z, sizeof(u8)); \
if (0 > __rc) { \
JOT(8, ":-(%i\n", __LINE__); return __rc; \
} \
} while (0)
#define SET(X, Y, Z) do { \
int __rc; \
__rc = regset(X, Y, Z); \
if (0 > __rc) { \
JOT(8, ":-(%i\n", __LINE__); return __rc; \
} \
} while (0)
/*--------------------------------------------------------------------------*/
static const struct stk1160config {
u16 reg;
u16 set;
} stk1160configPAL[] = {
{0x000, 0x0098},
{0x002, 0x0093},
{0x001, 0x0003},
{0x003, 0x0080},
{0x00D, 0x0000},
{0x00F, 0x0002},
{0x018, 0x0010},
{0x019, 0x0000},
{0x01A, 0x0014},
{0x01B, 0x000E},
{0x01C, 0x0046},
{0x100, 0x0033},
{0x103, 0x0000},
{0x104, 0x0000},
{0x105, 0x0000},
{0x106, 0x0000},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* RESOLUTION 640x480
*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{0x110, 0x0008},
{0x111, 0x0000},
{0x112, 0x0020},
{0x113, 0x0000},
{0x114, 0x0508},
{0x115, 0x0005},
{0x116, 0x0110},
{0x117, 0x0001},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{0x202, 0x000F},
{0x203, 0x004A},
{0x2FF, 0x0000},
{0xFFF, 0xFFFF}
};
/*--------------------------------------------------------------------------*/
static const struct stk1160config stk1160configNTSC[] = {
{0x000, 0x0098},
{0x002, 0x0093},
{0x001, 0x0003},
{0x003, 0x0080},
{0x00D, 0x0000},
{0x00F, 0x0002},
{0x018, 0x0010},
{0x019, 0x0000},
{0x01A, 0x0014},
{0x01B, 0x000E},
{0x01C, 0x0046},
{0x100, 0x0033},
{0x103, 0x0000},
{0x104, 0x0000},
{0x105, 0x0000},
{0x106, 0x0000},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* RESOLUTION 640x480
*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{0x110, 0x0008},
{0x111, 0x0000},
{0x112, 0x0003},
{0x113, 0x0000},
{0x114, 0x0508},
{0x115, 0x0005},
{0x116, 0x00F3},
{0x117, 0x0000},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{0x202, 0x000F},
{0x203, 0x004A},
{0x2FF, 0x0000},
{0xFFF, 0xFFFF}
};
/*--------------------------------------------------------------------------*/
static const struct saa7113config {
u8 reg;
u8 set;
} saa7113configPAL[] = {
{0x01, 0x08},
{0x02, 0x80},
{0x03, 0x33},
{0x04, 0x00},
{0x05, 0x00},
{0x06, 0xE9},
{0x07, 0x0D},
{0x08, 0x38},
{0x09, 0x00},
{0x0A, SAA_0A_DEFAULT},
{0x0B, SAA_0B_DEFAULT},
{0x0C, SAA_0C_DEFAULT},
{0x0D, SAA_0D_DEFAULT},
{0x0E, 0x01},
{0x0F, 0x36},
{0x10, 0x00},
{0x11, 0x0C},
{0x12, 0xE7},
{0x13, 0x00},
{0x15, 0x00},
{0x16, 0x00},
{0x40, 0x02},
{0x41, 0xFF},
{0x42, 0xFF},
{0x43, 0xFF},
{0x44, 0xFF},
{0x45, 0xFF},
{0x46, 0xFF},
{0x47, 0xFF},
{0x48, 0xFF},
{0x49, 0xFF},
{0x4A, 0xFF},
{0x4B, 0xFF},
{0x4C, 0xFF},
{0x4D, 0xFF},
{0x4E, 0xFF},
{0x4F, 0xFF},
{0x50, 0xFF},
{0x51, 0xFF},
{0x52, 0xFF},
{0x53, 0xFF},
{0x54, 0xFF},
{0x55, 0xFF},
{0x56, 0xFF},
{0x57, 0xFF},
{0x58, 0x40},
{0x59, 0x54},
{0x5A, 0x07},
{0x5B, 0x83},
{0xFF, 0xFF}
};
/*--------------------------------------------------------------------------*/
static const struct saa7113config saa7113configNTSC[] = {
{0x01, 0x08},
{0x02, 0x80},
{0x03, 0x33},
{0x04, 0x00},
{0x05, 0x00},
{0x06, 0xE9},
{0x07, 0x0D},
{0x08, 0x78},
{0x09, 0x00},
{0x0A, SAA_0A_DEFAULT},
{0x0B, SAA_0B_DEFAULT},
{0x0C, SAA_0C_DEFAULT},
{0x0D, SAA_0D_DEFAULT},
{0x0E, 0x01},
{0x0F, 0x36},
{0x10, 0x00},
{0x11, 0x0C},
{0x12, 0xE7},
{0x13, 0x00},
{0x15, 0x00},
{0x16, 0x00},
{0x40, 0x82},
{0x41, 0xFF},
{0x42, 0xFF},
{0x43, 0xFF},
{0x44, 0xFF},
{0x45, 0xFF},
{0x46, 0xFF},
{0x47, 0xFF},
{0x48, 0xFF},
{0x49, 0xFF},
{0x4A, 0xFF},
{0x4B, 0xFF},
{0x4C, 0xFF},
{0x4D, 0xFF},
{0x4E, 0xFF},
{0x4F, 0xFF},
{0x50, 0xFF},
{0x51, 0xFF},
{0x52, 0xFF},
{0x53, 0xFF},
{0x54, 0xFF},
{0x55, 0xFF},
{0x56, 0xFF},
{0x57, 0xFF},
{0x58, 0x40},
{0x59, 0x54},
{0x5A, 0x0A},
{0x5B, 0x83},
{0xFF, 0xFF}
};
static int regget(struct usb_device *pusb_device,
u16 index, void *reg, int reg_size)
{
int rc;
if (!pusb_device)
return -ENODEV;
rc = usb_control_msg(pusb_device, usb_rcvctrlpipe(pusb_device, 0),
0x00,
(USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE),
0x00,
index, reg, reg_size, 50000);
return rc;
}
static int regset(struct usb_device *pusb_device, u16 index, u16 value)
{
int rc;
if (!pusb_device)
return -ENODEV;
rc = usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0),
0x01,
(USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE),
value, index, NULL, 0, 500);
if (rc < 0)
return rc;
if (easycap_readback) {
u16 igot = 0;
rc = regget(pusb_device, index, &igot, sizeof(igot));
igot = 0xFF & igot;
switch (index) {
case 0x000:
case 0x500:
case 0x502:
case 0x503:
case 0x504:
case 0x506:
case 0x507:
break;
case 0x204:
case 0x205:
case 0x350:
case 0x351:
if (igot)
JOT(8, "unexpected 0x%02X "
"for STK register 0x%03X\n",
igot, index);
break;
default:
if ((0xFF & value) != igot)
JOT(8, "unexpected 0x%02X != 0x%02X "
"for STK register 0x%03X\n",
igot, value, index);
break;
}
}
return rc;
}
/*--------------------------------------------------------------------------*/
/*
* FUNCTION wait_i2c() RETURNS 0 ON SUCCESS
*/
/*--------------------------------------------------------------------------*/
static int wait_i2c(struct usb_device *p)
{
u16 get0;
u8 igot;
const int max = 2;
int k;
if (!p)
return -ENODEV;
for (k = 0; k < max; k++) {
GET(p, 0x0201, &igot); get0 = igot;
switch (get0) {
case 0x04:
case 0x01:
return 0;
case 0x00:
msleep(20);
continue;
default:
return get0 - 1;
}
}
return -1;
}
/****************************************************************************/
int write_saa(struct usb_device *p, u16 reg0, u16 set0)
{
if (!p)
return -ENODEV;
SET(p, 0x200, 0x00);
SET(p, 0x204, reg0);
SET(p, 0x205, set0);
SET(p, 0x200, 0x01);
return wait_i2c(p);
}
/****************************************************************************/
/*--------------------------------------------------------------------------*/
/*
* REGISTER 500: SETTING VALUE TO 0x008B READS FROM VT1612A (?)
* REGISTER 500: SETTING VALUE TO 0x008C WRITES TO VT1612A
* REGISTER 502: LEAST SIGNIFICANT BYTE OF VALUE TO SET
* REGISTER 503: MOST SIGNIFICANT BYTE OF VALUE TO SET
* REGISTER 504: TARGET ADDRESS ON VT1612A
*/
/*--------------------------------------------------------------------------*/
static int write_vt(struct usb_device *p, u16 reg0, u16 set0)
{
u8 igot;
u16 got502, got503;
u16 set502, set503;
if (!p)
return -ENODEV;
SET(p, 0x0504, reg0);
SET(p, 0x0500, 0x008B);
GET(p, 0x0502, &igot); got502 = (0xFF & igot);
GET(p, 0x0503, &igot); got503 = (0xFF & igot);
JOT(16, "write_vt(., 0x%04X, 0x%04X): was 0x%04X\n",
reg0, set0, ((got503 << 8) | got502));
set502 = (0x00FF & set0);
set503 = ((0xFF00 & set0) >> 8);
SET(p, 0x0504, reg0);
SET(p, 0x0502, set502);
SET(p, 0x0503, set503);
SET(p, 0x0500, 0x008C);
return 0;
}
/****************************************************************************/
/*--------------------------------------------------------------------------*/
/*
* REGISTER 500: SETTING VALUE TO 0x008B READS FROM VT1612A (?)
* REGISTER 500: SETTING VALUE TO 0x008C WRITES TO VT1612A
* REGISTER 502: LEAST SIGNIFICANT BYTE OF VALUE TO GET
* REGISTER 503: MOST SIGNIFICANT BYTE OF VALUE TO GET
* REGISTER 504: TARGET ADDRESS ON VT1612A
*/
/*--------------------------------------------------------------------------*/
static int read_vt(struct usb_device *p, u16 reg0)
{
u8 igot;
u16 got502, got503;
if (!p)
return -ENODEV;
SET(p, 0x0504, reg0);
SET(p, 0x0500, 0x008B);
GET(p, 0x0502, &igot); got502 = (0xFF & igot);
GET(p, 0x0503, &igot); got503 = (0xFF & igot);
JOT(16, "read_vt(., 0x%04X): has 0x%04X\n",
reg0, ((got503 << 8) | got502));
return (got503 << 8) | got502;
}
/****************************************************************************/
/*--------------------------------------------------------------------------*/
/*
* THESE APPEAR TO HAVE NO EFFECT ON EITHER VIDEO OR AUDIO.
*/
/*--------------------------------------------------------------------------*/
static int write_300(struct usb_device *p)
{
if (!p)
return -ENODEV;
SET(p, 0x300, 0x0012);
SET(p, 0x350, 0x002D);
SET(p, 0x351, 0x0001);
SET(p, 0x352, 0x0000);
SET(p, 0x353, 0x0000);
SET(p, 0x300, 0x0080);
return 0;
}
/****************************************************************************/
/****************************************************************************/
int setup_stk(struct usb_device *p, bool ntsc)
{
int i;
const struct stk1160config *cfg;
if (!p)
return -ENODEV;
cfg = (ntsc) ? stk1160configNTSC : stk1160configPAL;
for (i = 0; cfg[i].reg != 0xFFF; i++)
SET(p, cfg[i].reg, cfg[i].set);
write_300(p);
return 0;
}
/****************************************************************************/
int setup_saa(struct usb_device *p, bool ntsc)
{
int i, rc;
const struct saa7113config *cfg;
if (!p)
return -ENODEV;
cfg = (ntsc) ? saa7113configNTSC : saa7113configPAL;
for (i = 0; cfg[i].reg != 0xFF; i++) {
rc = write_saa(p, cfg[i].reg, cfg[i].set);
if (rc)
dev_err(&p->dev,
"Failed to set SAA register %d", cfg[i].reg);
}
return 0;
}
/****************************************************************************/
int merit_saa(struct usb_device *p)
{
int rc;
if (!p)
return -ENODEV;
rc = read_saa(p, 0x1F);
return ((0 > rc) || (0x02 & rc)) ? 1 : 0;
}
/****************************************************************************/
int ready_saa(struct usb_device *p)
{
int j, rc, rate;
const int max = 5, marktime = PATIENCE/5;
/*--------------------------------------------------------------------------*/
/*
* RETURNS 0 FOR INTERLACED 50 Hz
* 1 FOR NON-INTERLACED 50 Hz
* 2 FOR INTERLACED 60 Hz
* 3 FOR NON-INTERLACED 60 Hz
*/
/*--------------------------------------------------------------------------*/
if (!p)
return -ENODEV;
j = 0;
while (max > j) {
rc = read_saa(p, 0x1F);
if (0 <= rc) {
if (0 == (0x40 & rc))
break;
if (1 == (0x01 & rc))
break;
}
msleep(marktime);
j++;
}
if (max == j)
return -1;
if (0x20 & rc) {
rate = 2;
JOT(8, "hardware detects 60 Hz\n");
} else {
rate = 0;
JOT(8, "hardware detects 50 Hz\n");
}
if (0x80 & rc)
JOT(8, "hardware detects interlacing\n");
else {
rate++;
JOT(8, "hardware detects no interlacing\n");
}
return 0;
}
/****************************************************************************/
int read_saa(struct usb_device *p, u16 reg0)
{
u8 igot;
if (!p)
return -ENODEV;
SET(p, 0x208, reg0);
SET(p, 0x200, 0x20);
if (0 != wait_i2c(p))
return -1;
igot = 0;
GET(p, 0x0209, &igot);
return igot;
}
/****************************************************************************/
static int read_stk(struct usb_device *p, u32 reg0)
{
u8 igot;
if (!p)
return -ENODEV;
igot = 0;
GET(p, reg0, &igot);
return igot;
}
int select_input(struct usb_device *p, int input, int mode)
{
int ir;
if (!p)
return -ENODEV;
stop_100(p);
switch (input) {
case 0:
case 1: {
if (0 != write_saa(p, 0x02, 0x80))
SAY("ERROR: failed to set SAA register 0x02 "
"for input %i\n", input);
SET(p, 0x0000, 0x0098);
SET(p, 0x0002, 0x0078);
break;
}
case 2: {
if (0 != write_saa(p, 0x02, 0x80))
SAY("ERROR: failed to set SAA register 0x02 "
"for input %i\n", input);
SET(p, 0x0000, 0x0090);
SET(p, 0x0002, 0x0078);
break;
}
case 3: {
if (0 != write_saa(p, 0x02, 0x80))
SAY("ERROR: failed to set SAA register 0x02 "
" for input %i\n", input);
SET(p, 0x0000, 0x0088);
SET(p, 0x0002, 0x0078);
break;
}
case 4: {
if (0 != write_saa(p, 0x02, 0x80)) {
SAY("ERROR: failed to set SAA register 0x02 "
"for input %i\n", input);
}
SET(p, 0x0000, 0x0080);
SET(p, 0x0002, 0x0078);
break;
}
case 5: {
if (9 != mode)
mode = 7;
switch (mode) {
case 7: {
if (0 != write_saa(p, 0x02, 0x87))
SAY("ERROR: failed to set SAA register 0x02 "
"for input %i\n", input);
if (0 != write_saa(p, 0x05, 0xFF))
SAY("ERROR: failed to set SAA register 0x05 "
"for input %i\n", input);
break;
}
case 9: {
if (0 != write_saa(p, 0x02, 0x89))
SAY("ERROR: failed to set SAA register 0x02 "
"for input %i\n", input);
if (0 != write_saa(p, 0x05, 0x00))
SAY("ERROR: failed to set SAA register 0x05 "
"for input %i\n", input);
break;
}
default:
SAY("MISTAKE: bad mode: %i\n", mode);
return -1;
}
if (0 != write_saa(p, 0x04, 0x00))
SAY("ERROR: failed to set SAA register 0x04 "
"for input %i\n", input);
if (0 != write_saa(p, 0x09, 0x80))
SAY("ERROR: failed to set SAA register 0x09 "
"for input %i\n", input);
SET(p, 0x0002, 0x0093);
break;
}
default:
SAY("ERROR: bad input: %i\n", input);
return -1;
}
ir = read_stk(p, 0x00);
JOT(8, "STK register 0x00 has 0x%02X\n", ir);
ir = read_saa(p, 0x02);
JOT(8, "SAA register 0x02 has 0x%02X\n", ir);
start_100(p);
return 0;
}
/****************************************************************************/
int set_resolution(struct usb_device *p,
u16 set0, u16 set1, u16 set2, u16 set3)
{
u16 u0x0111, u0x0113, u0x0115, u0x0117;
if (!p)
return -ENODEV;
u0x0111 = ((0xFF00 & set0) >> 8);
u0x0113 = ((0xFF00 & set1) >> 8);
u0x0115 = ((0xFF00 & set2) >> 8);
u0x0117 = ((0xFF00 & set3) >> 8);
SET(p, 0x0110, (0x00FF & set0));
SET(p, 0x0111, u0x0111);
SET(p, 0x0112, (0x00FF & set1));
SET(p, 0x0113, u0x0113);
SET(p, 0x0114, (0x00FF & set2));
SET(p, 0x0115, u0x0115);
SET(p, 0x0116, (0x00FF & set3));
SET(p, 0x0117, u0x0117);
return 0;
}
/****************************************************************************/
int start_100(struct usb_device *p)
{
u16 get116, get117, get0;
u8 igot116, igot117, igot;
if (!p)
return -ENODEV;
GET(p, 0x0116, &igot116);
get116 = igot116;
GET(p, 0x0117, &igot117);
get117 = igot117;
SET(p, 0x0116, 0x0000);
SET(p, 0x0117, 0x0000);
GET(p, 0x0100, &igot);
get0 = igot;
SET(p, 0x0100, (0x80 | get0));
SET(p, 0x0116, get116);
SET(p, 0x0117, get117);
return 0;
}
/****************************************************************************/
int stop_100(struct usb_device *p)
{
u16 get0;
u8 igot;
if (!p)
return -ENODEV;
GET(p, 0x0100, &igot);
get0 = igot;
SET(p, 0x0100, (0x7F & get0));
return 0;
}
/****************************************************************************/
/****************************************************************************/
/*****************************************************************************/
int easycap_wakeup_device(struct usb_device *pusb_device)
{
if (!pusb_device)
return -ENODEV;
return usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0),
USB_REQ_SET_FEATURE,
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
USB_DEVICE_REMOTE_WAKEUP,
0, NULL, 0, 50000);
}
/*****************************************************************************/
int easycap_audio_setup(struct easycap *peasycap)
{
struct usb_device *pusb_device;
u8 buffer[1];
int rc, id1, id2;
/*---------------------------------------------------------------------------*/
/*
* IMPORTANT:
* THE MESSAGE OF TYPE (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)
* CAUSES MUTING IF THE VALUE 0x0100 IS SENT.
* TO ENABLE AUDIO THE VALUE 0x0200 MUST BE SENT.
*/
/*---------------------------------------------------------------------------*/
const u8 request = 0x01;
const u8 requesttype = USB_DIR_OUT |
USB_TYPE_CLASS |
USB_RECIP_INTERFACE;
const u16 value_unmute = 0x0200;
const u16 index = 0x0301;
const u16 length = 1;
if (!peasycap)
return -EFAULT;
pusb_device = peasycap->pusb_device;
if (!pusb_device)
return -ENODEV;
JOM(8, "%02X %02X %02X %02X %02X %02X %02X %02X\n",
requesttype, request,
(0x00FF & value_unmute),
(0xFF00 & value_unmute) >> 8,
(0x00FF & index),
(0xFF00 & index) >> 8,
(0x00FF & length),
(0xFF00 & length) >> 8);
buffer[0] = 0x01;
rc = usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0),
request, requesttype, value_unmute,
index, &buffer[0], length, 50000);
JOT(8, "0x%02X=buffer\n", buffer[0]);
if (rc != (int)length) {
switch (rc) {
case -EPIPE:
SAY("usb_control_msg returned -EPIPE\n");
break;
default:
SAY("ERROR: usb_control_msg returned %i\n", rc);
break;
}
}
/*--------------------------------------------------------------------------*/
/*
* REGISTER 500: SETTING VALUE TO 0x0094 RESETS AUDIO CONFIGURATION ???
* REGISTER 506: ANALOGUE AUDIO ATTENTUATOR ???
* FOR THE CVBS+S-VIDEO HARDWARE:
* SETTING VALUE TO 0x0000 GIVES QUIET SOUND.
* THE UPPER BYTE SEEMS TO HAVE NO EFFECT.
* FOR THE FOUR-CVBS HARDWARE:
* SETTING VALUE TO 0x0000 SEEMS TO HAVE NO EFFECT.
* REGISTER 507: ANALOGUE AUDIO PREAMPLIFIER ON/OFF ???
* FOR THE CVBS-S-VIDEO HARDWARE:
* SETTING VALUE TO 0x0001 GIVES VERY LOUD, DISTORTED SOUND.
* THE UPPER BYTE SEEMS TO HAVE NO EFFECT.
*/
/*--------------------------------------------------------------------------*/
SET(pusb_device, 0x0500, 0x0094);
SET(pusb_device, 0x0500, 0x008C);
SET(pusb_device, 0x0506, 0x0001);
SET(pusb_device, 0x0507, 0x0000);
id1 = read_vt(pusb_device, 0x007C);
id2 = read_vt(pusb_device, 0x007E);
SAM("0x%04X:0x%04X is audio vendor id\n", id1, id2);
/*---------------------------------------------------------------------------*/
/*
* SELECT AUDIO SOURCE "LINE IN" AND SET THE AUDIO GAIN.
*/
/*---------------------------------------------------------------------------*/
if (easycap_audio_gainset(pusb_device, peasycap->gain))
SAY("ERROR: audio_gainset() failed\n");
check_vt(pusb_device);
return 0;
}
/*****************************************************************************/
int check_vt(struct usb_device *pusb_device)
{
int igot;
if (!pusb_device)
return -ENODEV;
igot = read_vt(pusb_device, 0x0002);
if (0 > igot)
SAY("ERROR: failed to read VT1612A register 0x02\n");
if (0x8000 & igot)
SAY("register 0x%02X muted\n", 0x02);
igot = read_vt(pusb_device, 0x000E);
if (0 > igot)
SAY("ERROR: failed to read VT1612A register 0x0E\n");
if (0x8000 & igot)
SAY("register 0x%02X muted\n", 0x0E);
igot = read_vt(pusb_device, 0x0010);
if (0 > igot)
SAY("ERROR: failed to read VT1612A register 0x10\n");
if (0x8000 & igot)
SAY("register 0x%02X muted\n", 0x10);
igot = read_vt(pusb_device, 0x0012);
if (0 > igot)
SAY("ERROR: failed to read VT1612A register 0x12\n");
if (0x8000 & igot)
SAY("register 0x%02X muted\n", 0x12);
igot = read_vt(pusb_device, 0x0014);
if (0 > igot)
SAY("ERROR: failed to read VT1612A register 0x14\n");
if (0x8000 & igot)
SAY("register 0x%02X muted\n", 0x14);
igot = read_vt(pusb_device, 0x0016);
if (0 > igot)
SAY("ERROR: failed to read VT1612A register 0x16\n");
if (0x8000 & igot)
SAY("register 0x%02X muted\n", 0x16);
igot = read_vt(pusb_device, 0x0018);
if (0 > igot)
SAY("ERROR: failed to read VT1612A register 0x18\n");
if (0x8000 & igot)
SAY("register 0x%02X muted\n", 0x18);
igot = read_vt(pusb_device, 0x001C);
if (0 > igot)
SAY("ERROR: failed to read VT1612A register 0x1C\n");
if (0x8000 & igot)
SAY("register 0x%02X muted\n", 0x1C);
return 0;
}
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/* NOTE: THIS DOES INCREASE THE VOLUME DRAMATICALLY:
* audio_gainset(pusb_device, 0x000F);
*
* loud dB register 0x10 dB register 0x1C dB total
* 0 -34.5 0 -34.5
* .. .... . ....
* 15 10.5 0 10.5
* 16 12.0 0 12.0
* 17 12.0 1.5 13.5
* .. .... .... ....
* 31 12.0 22.5 34.5
*/
/*---------------------------------------------------------------------------*/
int easycap_audio_gainset(struct usb_device *pusb_device, s8 loud)
{
int igot;
u8 tmp;
u16 mute;
if (!pusb_device)
return -ENODEV;
if (0 > loud)
loud = 0;
if (31 < loud)
loud = 31;
write_vt(pusb_device, 0x0002, 0x8000);
/*---------------------------------------------------------------------------*/
igot = read_vt(pusb_device, 0x000E);
if (0 > igot) {
SAY("ERROR: failed to read VT1612A register 0x0E\n");
mute = 0x0000;
} else
mute = 0x8000 & ((unsigned int)igot);
mute = 0;
if (16 > loud)
tmp = 0x01 | (0x001F & (((u8)(15 - loud)) << 1));
else
tmp = 0;
JOT(8, "0x%04X=(mute|tmp) for VT1612A register 0x0E\n", mute | tmp);
write_vt(pusb_device, 0x000E, (mute | tmp));
/*---------------------------------------------------------------------------*/
igot = read_vt(pusb_device, 0x0010);
if (0 > igot) {
SAY("ERROR: failed to read VT1612A register 0x10\n");
mute = 0x0000;
} else
mute = 0x8000 & ((unsigned int)igot);
mute = 0;
JOT(8, "0x%04X=(mute|tmp|(tmp<<8)) for VT1612A register 0x10,...0x18\n",
mute | tmp | (tmp << 8));
write_vt(pusb_device, 0x0010, (mute | tmp | (tmp << 8)));
write_vt(pusb_device, 0x0012, (mute | tmp | (tmp << 8)));
write_vt(pusb_device, 0x0014, (mute | tmp | (tmp << 8)));
write_vt(pusb_device, 0x0016, (mute | tmp | (tmp << 8)));
write_vt(pusb_device, 0x0018, (mute | tmp | (tmp << 8)));
/*---------------------------------------------------------------------------*/
igot = read_vt(pusb_device, 0x001C);
if (0 > igot) {
SAY("ERROR: failed to read VT1612A register 0x1C\n");
mute = 0x0000;
} else
mute = 0x8000 & ((unsigned int)igot);
mute = 0;
if (16 <= loud)
tmp = 0x000F & (u8)(loud - 16);
else
tmp = 0;
JOT(8, "0x%04X=(mute|tmp|(tmp<<8)) for VT1612A register 0x1C\n",
mute | tmp | (tmp << 8));
write_vt(pusb_device, 0x001C, (mute | tmp | (tmp << 8)));
write_vt(pusb_device, 0x001A, 0x0404);
write_vt(pusb_device, 0x0002, 0x0000);
return 0;
}
/*****************************************************************************/
/******************************************************************************
* *
* easycap_main.c *
* *
* Video driver for EasyCAP USB2.0 Video Capture Device DC60 *
* *
* *
******************************************************************************/
/*
*
* Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org>
*
*
* This 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, or
* (at your option) any later version.
*
* The software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*****************************************************************************/
#include "easycap.h"
#include <linux/usb/audio.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("R.M. Thomas <rmthomas@sciolus.org>");
MODULE_DESCRIPTION(EASYCAP_DRIVER_DESCRIPTION);
MODULE_VERSION(EASYCAP_DRIVER_VERSION);
#ifdef CONFIG_EASYCAP_DEBUG
int easycap_debug;
module_param_named(debug, easycap_debug, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug level: 0(default),1,2,...,9");
#endif /* CONFIG_EASYCAP_DEBUG */
bool easycap_readback;
module_param_named(readback, easycap_readback, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(readback, "read back written registers: (default false)");
static int easycap_bars = 1;
module_param_named(bars, easycap_bars, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(bars,
"Testcard bars on input signal failure: 0=>no, 1=>yes(default)");
static int easycap_gain = 16;
module_param_named(gain, easycap_gain, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(gain, "Audio gain: 0,...,16(default),...31");
static bool easycap_ntsc;
module_param_named(ntsc, easycap_ntsc, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(ntsc, "NTSC default encoding (default PAL)");
struct easycap_dongle easycapdc60_dongle[DONGLE_MANY];
static struct mutex mutex_dongle;
static void easycap_complete(struct urb *purb);
static int reset(struct easycap *peasycap);
static int field2frame(struct easycap *peasycap);
static int redaub(struct easycap *peasycap,
void *pad, void *pex, int much, int more,
u8 mask, u8 margin, bool isuy);
const char *strerror(int err)
{
#define ERRNOSTR(_e) case _e: return # _e
switch (err) {
case 0: return "OK";
ERRNOSTR(ENOMEM);
ERRNOSTR(ENODEV);
ERRNOSTR(ENXIO);
ERRNOSTR(EINVAL);
ERRNOSTR(EAGAIN);
ERRNOSTR(EFBIG);
ERRNOSTR(EPIPE);
ERRNOSTR(EMSGSIZE);
ERRNOSTR(ENOSPC);
ERRNOSTR(EINPROGRESS);
ERRNOSTR(ENOSR);
ERRNOSTR(EOVERFLOW);
ERRNOSTR(EPROTO);
ERRNOSTR(EILSEQ);
ERRNOSTR(ETIMEDOUT);
ERRNOSTR(EOPNOTSUPP);
ERRNOSTR(EPFNOSUPPORT);
ERRNOSTR(EAFNOSUPPORT);
ERRNOSTR(EADDRINUSE);
ERRNOSTR(EADDRNOTAVAIL);
ERRNOSTR(ENOBUFS);
ERRNOSTR(EISCONN);
ERRNOSTR(ENOTCONN);
ERRNOSTR(ESHUTDOWN);
ERRNOSTR(ENOENT);
ERRNOSTR(ECONNRESET);
ERRNOSTR(ETIME);
ERRNOSTR(ECOMM);
ERRNOSTR(EREMOTEIO);
ERRNOSTR(EXDEV);
ERRNOSTR(EPERM);
default: return "unknown";
}
#undef ERRNOSTR
}
/****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* THIS ROUTINE DOES NOT DETECT DUPLICATE OCCURRENCES OF POINTER peasycap
*/
/*---------------------------------------------------------------------------*/
int easycap_isdongle(struct easycap *peasycap)
{
int k;
if (!peasycap)
return -2;
for (k = 0; k < DONGLE_MANY; k++) {
if (easycapdc60_dongle[k].peasycap == peasycap) {
peasycap->isdongle = k;
return k;
}
}
return -1;
}
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
static int easycap_open(struct inode *inode, struct file *file)
{
struct video_device *pvideo_device;
struct easycap *peasycap;
int rc;
JOT(4, "\n");
SAY("==========OPEN=========\n");
pvideo_device = video_devdata(file);
if (!pvideo_device) {
SAY("ERROR: pvideo_device is NULL.\n");
return -EFAULT;
}
peasycap = (struct easycap *)video_get_drvdata(pvideo_device);
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
JOM(16, "peasycap->pusb_device=%p\n", peasycap->pusb_device);
file->private_data = peasycap;
rc = easycap_wakeup_device(peasycap->pusb_device);
if (rc) {
SAM("ERROR: wakeup_device() rc = %i\n", rc);
if (-ENODEV == rc)
SAM("ERROR: wakeup_device() returned -ENODEV\n");
else
SAM("ERROR: wakeup_device() rc = %i\n", rc);
return rc;
}
JOM(8, "wakeup_device() OK\n");
peasycap->input = 0;
rc = reset(peasycap);
if (rc) {
SAM("ERROR: reset() rc = %i\n", rc);
return -EFAULT;
}
return 0;
}
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* RESET THE HARDWARE TO ITS REFERENCE STATE.
*
* THIS ROUTINE MAY BE CALLED REPEATEDLY IF easycap_complete() DETECTS
* A BAD VIDEO FRAME SIZE.
*/
/*---------------------------------------------------------------------------*/
static int reset(struct easycap *peasycap)
{
struct easycap_standard const *peasycap_standard;
int fmtidx, input, rate;
bool ntsc, other;
int rc;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
input = peasycap->input;
/*---------------------------------------------------------------------------*/
/*
* IF THE SAA7113H HAS ALREADY ACQUIRED SYNC, USE ITS HARDWARE-DETECTED
* FIELD FREQUENCY TO DISTINGUISH NTSC FROM PAL. THIS IS ESSENTIAL FOR
* gstreamer AND OTHER USERSPACE PROGRAMS WHICH MAY NOT ATTEMPT TO INITIATE
* A SWITCH BETWEEN PAL AND NTSC.
*
* FUNCTION ready_saa() MAY REQUIRE A SUBSTANTIAL FRACTION OF A SECOND TO
* COMPLETE, SO SHOULD NOT BE INVOKED WITHOUT GOOD REASON.
*/
/*---------------------------------------------------------------------------*/
other = false;
JOM(8, "peasycap->ntsc=%d\n", peasycap->ntsc);
rate = ready_saa(peasycap->pusb_device);
if (rate < 0) {
JOM(8, "not ready to capture after %i ms ...\n", PATIENCE);
ntsc = !peasycap->ntsc;
JOM(8, "... trying %s ..\n", ntsc ? "NTSC" : "PAL");
rc = setup_stk(peasycap->pusb_device, ntsc);
if (rc) {
SAM("ERROR: setup_stk() rc = %i\n", rc);
return -EFAULT;
}
rc = setup_saa(peasycap->pusb_device, ntsc);
if (rc) {
SAM("ERROR: setup_saa() rc = %i\n", rc);
return -EFAULT;
}
rate = ready_saa(peasycap->pusb_device);
if (rate < 0) {
JOM(8, "not ready to capture after %i ms\n", PATIENCE);
JOM(8, "... saa register 0x1F has 0x%02X\n",
read_saa(peasycap->pusb_device, 0x1F));
ntsc = peasycap->ntsc;
} else {
JOM(8, "... success at second try: %i=rate\n", rate);
ntsc = (0 < (rate/2)) ? true : false ;
other = true;
}
} else {
JOM(8, "... success at first try: %i=rate\n", rate);
ntsc = (0 < rate/2) ? true : false ;
}
JOM(8, "ntsc=%d\n", ntsc);
/*---------------------------------------------------------------------------*/
rc = setup_stk(peasycap->pusb_device, ntsc);
if (rc) {
SAM("ERROR: setup_stk() rc = %i\n", rc);
return -EFAULT;
}
rc = setup_saa(peasycap->pusb_device, ntsc);
if (rc) {
SAM("ERROR: setup_saa() rc = %i\n", rc);
return -EFAULT;
}
memset(peasycap->merit, 0, sizeof(peasycap->merit));
peasycap->video_eof = 0;
peasycap->audio_eof = 0;
/*---------------------------------------------------------------------------*/
/*
* RESTORE INPUT AND FORCE REFRESH OF STANDARD, FORMAT, ETC.
*
* WHILE THIS PROCEDURE IS IN PROGRESS, SOME IOCTL COMMANDS WILL RETURN -EBUSY.
*/
/*---------------------------------------------------------------------------*/
peasycap->input = -8192;
peasycap->standard_offset = -8192;
fmtidx = ntsc ? NTSC_M : PAL_BGHIN;
if (other) {
peasycap_standard = &easycap_standard[0];
while (0xFFFF != peasycap_standard->mask) {
if (fmtidx == peasycap_standard->v4l2_standard.index) {
peasycap->inputset[input].standard_offset =
peasycap_standard - easycap_standard;
break;
}
peasycap_standard++;
}
if (0xFFFF == peasycap_standard->mask) {
SAM("ERROR: standard not found\n");
return -EINVAL;
}
JOM(8, "%i=peasycap->inputset[%i].standard_offset\n",
peasycap->inputset[input].standard_offset, input);
}
peasycap->format_offset = -8192;
peasycap->brightness = -8192;
peasycap->contrast = -8192;
peasycap->saturation = -8192;
peasycap->hue = -8192;
rc = easycap_newinput(peasycap, input);
if (rc) {
SAM("ERROR: newinput(.,%i) rc = %i\n", rc, input);
return -EFAULT;
}
JOM(4, "restored input, standard and format\n");
JOM(8, "true=peasycap->ntsc %d\n", peasycap->ntsc);
if (0 > peasycap->input) {
SAM("MISTAKE: %i=peasycap->input\n", peasycap->input);
return -ENOENT;
}
if (0 > peasycap->standard_offset) {
SAM("MISTAKE: %i=peasycap->standard_offset\n",
peasycap->standard_offset);
return -ENOENT;
}
if (0 > peasycap->format_offset) {
SAM("MISTAKE: %i=peasycap->format_offset\n",
peasycap->format_offset);
return -ENOENT;
}
if (0 > peasycap->brightness) {
SAM("MISTAKE: %i=peasycap->brightness\n",
peasycap->brightness);
return -ENOENT;
}
if (0 > peasycap->contrast) {
SAM("MISTAKE: %i=peasycap->contrast\n", peasycap->contrast);
return -ENOENT;
}
if (0 > peasycap->saturation) {
SAM("MISTAKE: %i=peasycap->saturation\n",
peasycap->saturation);
return -ENOENT;
}
if (0 > peasycap->hue) {
SAM("MISTAKE: %i=peasycap->hue\n", peasycap->hue);
return -ENOENT;
}
return 0;
}
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* IF THE REQUESTED INPUT IS THE SAME AS THE EXISTING INPUT, DO NOTHING.
* OTHERWISE:
* KILL URBS, CLEAR FIELD AND FRAME BUFFERS AND RESET THEIR
* _read AND _fill POINTERS.
* SELECT THE NEW INPUT.
* ADJUST THE STANDARD, FORMAT, BRIGHTNESS, CONTRAST, SATURATION AND HUE
* ON THE BASIS OF INFORMATION IN STRUCTURE easycap.inputset[input].
* RESUBMIT THE URBS IF STREAMING WAS ALREADY IN PROGRESS.
*
* NOTE:
* THIS ROUTINE MAY BE CALLED FREQUENTLY BY ZONEMINDER VIA IOCTL,
* SO IT SHOULD WRITE ONLY SPARINGLY TO THE LOGFILE.
*/
/*---------------------------------------------------------------------------*/
int easycap_newinput(struct easycap *peasycap, int input)
{
int rc, k, m, mood, off;
int inputnow, video_idlenow, audio_idlenow;
bool resubmit;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
JOM(8, "%i=input sought\n", input);
if (0 > input && INPUT_MANY <= input)
return -ENOENT;
inputnow = peasycap->input;
if (input == inputnow)
return 0;
/*---------------------------------------------------------------------------*/
/*
* IF STREAMING IS IN PROGRESS THE URBS ARE KILLED AT THIS
* STAGE AND WILL BE RESUBMITTED PRIOR TO EXIT FROM THE ROUTINE.
* IF NO STREAMING IS IN PROGRESS NO URBS WILL BE SUBMITTED BY THE
* ROUTINE.
*/
/*---------------------------------------------------------------------------*/
video_idlenow = peasycap->video_idle;
audio_idlenow = peasycap->audio_idle;
peasycap->video_idle = 1;
peasycap->audio_idle = 1;
if (peasycap->video_isoc_streaming) {
resubmit = true;
easycap_video_kill_urbs(peasycap);
} else {
resubmit = false;
}
/*---------------------------------------------------------------------------*/
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -ENODEV;
}
rc = usb_set_interface(peasycap->pusb_device,
peasycap->video_interface,
peasycap->video_altsetting_off);
if (rc) {
SAM("ERROR: usb_set_interface() rc = %i\n", rc);
return -EFAULT;
}
rc = stop_100(peasycap->pusb_device);
if (rc) {
SAM("ERROR: stop_100() rc = %i\n", rc);
return -EFAULT;
}
for (k = 0; k < FIELD_BUFFER_MANY; k++) {
for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++)
memset(peasycap->field_buffer[k][m].pgo, 0, PAGE_SIZE);
}
for (k = 0; k < FRAME_BUFFER_MANY; k++) {
for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++)
memset(peasycap->frame_buffer[k][m].pgo, 0, PAGE_SIZE);
}
peasycap->field_page = 0;
peasycap->field_read = 0;
peasycap->field_fill = 0;
peasycap->frame_read = 0;
peasycap->frame_fill = 0;
for (k = 0; k < peasycap->input; k++) {
(peasycap->frame_fill)++;
if (peasycap->frame_buffer_many <= peasycap->frame_fill)
peasycap->frame_fill = 0;
}
peasycap->input = input;
select_input(peasycap->pusb_device, peasycap->input, 9);
/*---------------------------------------------------------------------------*/
if (input == peasycap->inputset[input].input) {
off = peasycap->inputset[input].standard_offset;
if (off != peasycap->standard_offset) {
rc = adjust_standard(peasycap,
easycap_standard[off].v4l2_standard.id);
if (rc) {
SAM("ERROR: adjust_standard() rc = %i\n", rc);
return -EFAULT;
}
JOM(8, "%i=peasycap->standard_offset\n",
peasycap->standard_offset);
} else {
JOM(8, "%i=peasycap->standard_offset unchanged\n",
peasycap->standard_offset);
}
off = peasycap->inputset[input].format_offset;
if (off != peasycap->format_offset) {
struct v4l2_pix_format *pix =
&easycap_format[off].v4l2_format.fmt.pix;
rc = adjust_format(peasycap,
pix->width, pix->height,
pix->pixelformat, pix->field, false);
if (0 > rc) {
SAM("ERROR: adjust_format() rc = %i\n", rc);
return -EFAULT;
}
JOM(8, "%i=peasycap->format_offset\n",
peasycap->format_offset);
} else {
JOM(8, "%i=peasycap->format_offset unchanged\n",
peasycap->format_offset);
}
mood = peasycap->inputset[input].brightness;
if (mood != peasycap->brightness) {
rc = adjust_brightness(peasycap, mood);
if (rc) {
SAM("ERROR: adjust_brightness rc = %i\n", rc);
return -EFAULT;
}
JOM(8, "%i=peasycap->brightness\n",
peasycap->brightness);
}
mood = peasycap->inputset[input].contrast;
if (mood != peasycap->contrast) {
rc = adjust_contrast(peasycap, mood);
if (rc) {
SAM("ERROR: adjust_contrast rc = %i\n", rc);
return -EFAULT;
}
JOM(8, "%i=peasycap->contrast\n", peasycap->contrast);
}
mood = peasycap->inputset[input].saturation;
if (mood != peasycap->saturation) {
rc = adjust_saturation(peasycap, mood);
if (rc) {
SAM("ERROR: adjust_saturation rc = %i\n", rc);
return -EFAULT;
}
JOM(8, "%i=peasycap->saturation\n",
peasycap->saturation);
}
mood = peasycap->inputset[input].hue;
if (mood != peasycap->hue) {
rc = adjust_hue(peasycap, mood);
if (rc) {
SAM("ERROR: adjust_hue rc = %i\n", rc);
return -EFAULT;
}
JOM(8, "%i=peasycap->hue\n", peasycap->hue);
}
} else {
SAM("MISTAKE: easycap.inputset[%i] unpopulated\n", input);
return -ENOENT;
}
/*---------------------------------------------------------------------------*/
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -ENODEV;
}
rc = usb_set_interface(peasycap->pusb_device,
peasycap->video_interface,
peasycap->video_altsetting_on);
if (rc) {
SAM("ERROR: usb_set_interface() rc = %i\n", rc);
return -EFAULT;
}
rc = start_100(peasycap->pusb_device);
if (rc) {
SAM("ERROR: start_100() rc = %i\n", rc);
return -EFAULT;
}
if (resubmit)
easycap_video_submit_urbs(peasycap);
peasycap->video_isoc_sequence = VIDEO_ISOC_BUFFER_MANY - 1;
peasycap->video_idle = video_idlenow;
peasycap->audio_idle = audio_idlenow;
peasycap->video_junk = 0;
return 0;
}
/*****************************************************************************/
int easycap_video_submit_urbs(struct easycap *peasycap)
{
struct data_urb *pdata_urb;
struct urb *purb;
struct list_head *plist_head;
int j, isbad, nospc, m, rc;
int isbuf;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->purb_video_head) {
SAY("ERROR: peasycap->urb_video_head uninitialized\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAY("ERROR: peasycap->pusb_device is NULL\n");
return -ENODEV;
}
if (!peasycap->video_isoc_streaming) {
JOM(4, "submission of all video urbs\n");
isbad = 0; nospc = 0; m = 0;
list_for_each(plist_head, (peasycap->purb_video_head)) {
pdata_urb = list_entry(plist_head,
struct data_urb, list_head);
if (pdata_urb && pdata_urb->purb) {
purb = pdata_urb->purb;
isbuf = pdata_urb->isbuf;
purb->interval = 1;
purb->dev = peasycap->pusb_device;
purb->pipe =
usb_rcvisocpipe(peasycap->pusb_device,
peasycap->video_endpointnumber);
purb->transfer_flags = URB_ISO_ASAP;
purb->transfer_buffer =
peasycap->video_isoc_buffer[isbuf].pgo;
purb->transfer_buffer_length =
peasycap->video_isoc_buffer_size;
purb->complete = easycap_complete;
purb->context = peasycap;
purb->start_frame = 0;
purb->number_of_packets =
peasycap->video_isoc_framesperdesc;
for (j = 0; j < peasycap->video_isoc_framesperdesc; j++) {
purb->iso_frame_desc[j]. offset =
j * peasycap->video_isoc_maxframesize;
purb->iso_frame_desc[j]. length =
peasycap->video_isoc_maxframesize;
}
rc = usb_submit_urb(purb, GFP_KERNEL);
if (rc) {
isbad++;
SAM("ERROR: usb_submit_urb() failed "
"for urb with rc:-%s\n",
strerror(rc));
if (rc == -ENOSPC)
nospc++;
} else {
m++;
}
} else {
isbad++;
}
}
if (nospc) {
SAM("-ENOSPC=usb_submit_urb() for %i urbs\n", nospc);
SAM("..... possibly inadequate USB bandwidth\n");
peasycap->video_eof = 1;
}
if (isbad)
easycap_video_kill_urbs(peasycap);
else
peasycap->video_isoc_streaming = 1;
} else {
JOM(4, "already streaming video urbs\n");
}
return 0;
}
/*****************************************************************************/
int easycap_audio_kill_urbs(struct easycap *peasycap)
{
int m;
struct list_head *plist_head;
struct data_urb *pdata_urb;
if (!peasycap->audio_isoc_streaming)
return 0;
if (!peasycap->purb_audio_head) {
SAM("ERROR: peasycap->purb_audio_head is NULL\n");
return -EFAULT;
}
peasycap->audio_isoc_streaming = 0;
m = 0;
list_for_each(plist_head, peasycap->purb_audio_head) {
pdata_urb = list_entry(plist_head, struct data_urb, list_head);
if (pdata_urb && pdata_urb->purb) {
usb_kill_urb(pdata_urb->purb);
m++;
}
}
JOM(4, "%i audio urbs killed\n", m);
return 0;
}
int easycap_video_kill_urbs(struct easycap *peasycap)
{
int m;
struct list_head *plist_head;
struct data_urb *pdata_urb;
if (!peasycap->video_isoc_streaming)
return 0;
if (!peasycap->purb_video_head) {
SAM("ERROR: peasycap->purb_video_head is NULL\n");
return -EFAULT;
}
peasycap->video_isoc_streaming = 0;
JOM(4, "killing video urbs\n");
m = 0;
list_for_each(plist_head, (peasycap->purb_video_head)) {
pdata_urb = list_entry(plist_head, struct data_urb, list_head);
if (pdata_urb && pdata_urb->purb) {
usb_kill_urb(pdata_urb->purb);
m++;
}
}
JOM(4, "%i video urbs killed\n", m);
return 0;
}
/****************************************************************************/
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
/*--------------------------------------------------------------------------*/
static int easycap_open_noinode(struct file *file)
{
return easycap_open(NULL, file);
}
static int videodev_release(struct video_device *pvideo_device)
{
struct easycap *peasycap;
peasycap = video_get_drvdata(pvideo_device);
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
SAY("ending unsuccessfully\n");
return -EFAULT;
}
if (easycap_video_kill_urbs(peasycap)) {
SAM("ERROR: easycap_video_kill_urbs() failed\n");
return -EFAULT;
}
JOM(4, "ending successfully\n");
return 0;
}
/*****************************************************************************/
static unsigned int easycap_poll(struct file *file, poll_table *wait)
{
struct easycap *peasycap;
int rc, kd;
JOT(8, "\n");
if (NULL == ((poll_table *)wait))
JOT(8, "WARNING: poll table pointer is NULL ... continuing\n");
if (!file) {
SAY("ERROR: file pointer is NULL\n");
return -ERESTARTSYS;
}
peasycap = file->private_data;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAY("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
/*---------------------------------------------------------------------------*/
kd = easycap_isdongle(peasycap);
if (0 <= kd && DONGLE_MANY > kd) {
if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) {
SAY("ERROR: cannot down dongle[%i].mutex_video\n", kd);
return -ERESTARTSYS;
}
JOM(4, "locked dongle[%i].mutex_video\n", kd);
/*
* MEANWHILE, easycap_usb_disconnect() MAY HAVE FREED POINTER
* peasycap, IN WHICH CASE A REPEAT CALL TO isdongle() WILL FAIL.
* IF NECESSARY, BAIL OUT.
*/
if (kd != easycap_isdongle(peasycap)) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ERESTARTSYS;
}
if (!file) {
SAY("ERROR: file is NULL\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ERESTARTSYS;
}
peasycap = file->private_data;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ERESTARTSYS;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return -ERESTARTSYS;
}
} else
/*
* IF easycap_usb_disconnect() HAS ALREADY FREED POINTER peasycap
* BEFORE THE ATTEMPT TO ACQUIRE THE SEMAPHORE, isdongle() WILL
* HAVE FAILED. BAIL OUT.
*/
return -ERESTARTSYS;
/*---------------------------------------------------------------------------*/
rc = easycap_video_dqbuf(peasycap, 0);
peasycap->polled = 1;
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
if (rc)
return POLLERR;
return POLLIN | POLLRDNORM;
}
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* IF mode IS NONZERO THIS ROUTINE RETURNS -EAGAIN RATHER THAN BLOCKING.
*/
/*---------------------------------------------------------------------------*/
int easycap_video_dqbuf(struct easycap *peasycap, int mode)
{
int input, ifield, miss, rc;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAY("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
ifield = 0;
JOM(8, "%i=ifield\n", ifield);
/*---------------------------------------------------------------------------*/
/*
* CHECK FOR LOST INPUT SIGNAL.
*
* FOR THE FOUR-CVBS EasyCAP, THIS DOES NOT WORK AS EXPECTED.
* IF INPUT 0 IS PRESENT AND SYNC ACQUIRED, UNPLUGGING INPUT 4 DOES NOT
* RESULT IN SETTING BIT 0x40 ON REGISTER 0x1F, PRESUMABLY BECAUSE THERE
* IS FLYWHEELING ON INPUT 0. THE UPSHOT IS:
*
* INPUT 0 PLUGGED, INPUT 4 PLUGGED => SCREEN 0 OK, SCREEN 4 OK
* INPUT 0 PLUGGED, INPUT 4 UNPLUGGED => SCREEN 0 OK, SCREEN 4 BLACK
* INPUT 0 UNPLUGGED, INPUT 4 PLUGGED => SCREEN 0 BARS, SCREEN 4 OK
* INPUT 0 UNPLUGGED, INPUT 4 UNPLUGGED => SCREEN 0 BARS, SCREEN 4 BARS
*/
/*---------------------------------------------------------------------------*/
input = peasycap->input;
if (0 <= input && INPUT_MANY > input) {
rc = read_saa(peasycap->pusb_device, 0x1F);
if (0 <= rc) {
if (rc & 0x40)
peasycap->lost[input] += 1;
else
peasycap->lost[input] -= 2;
if (0 > peasycap->lost[input])
peasycap->lost[input] = 0;
else if ((2 * VIDEO_LOST_TOLERATE) < peasycap->lost[input])
peasycap->lost[input] = (2 * VIDEO_LOST_TOLERATE);
}
}
/*---------------------------------------------------------------------------*/
/*
* WAIT FOR FIELD ifield (0 => TOP, 1 => BOTTOM)
*/
/*---------------------------------------------------------------------------*/
miss = 0;
while ((peasycap->field_read == peasycap->field_fill) ||
(0 != (0xFF00 & peasycap->field_buffer
[peasycap->field_read][0].kount)) ||
(ifield != (0x00FF & peasycap->field_buffer
[peasycap->field_read][0].kount))) {
if (mode)
return -EAGAIN;
JOM(8, "first wait on wq_video, %i=field_read %i=field_fill\n",
peasycap->field_read, peasycap->field_fill);
if (0 != (wait_event_interruptible(peasycap->wq_video,
(peasycap->video_idle || peasycap->video_eof ||
((peasycap->field_read != peasycap->field_fill) &&
(0 == (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) &&
(ifield == (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))))))) {
SAM("aborted by signal\n");
return -EIO;
}
if (peasycap->video_idle) {
JOM(8, "%i=peasycap->video_idle returning -EAGAIN\n",
peasycap->video_idle);
return -EAGAIN;
}
if (peasycap->video_eof) {
JOM(8, "%i=peasycap->video_eof\n", peasycap->video_eof);
#if defined(PERSEVERE)
if (1 == peasycap->status) {
JOM(8, "persevering ...\n");
peasycap->video_eof = 0;
peasycap->audio_eof = 0;
if (0 != reset(peasycap)) {
JOM(8, " ... failed returning -EIO\n");
peasycap->video_eof = 1;
peasycap->audio_eof = 1;
easycap_video_kill_urbs(peasycap);
return -EIO;
}
peasycap->status = 0;
JOM(8, " ... OK returning -EAGAIN\n");
return -EAGAIN;
}
#endif /*PERSEVERE*/
peasycap->video_eof = 1;
peasycap->audio_eof = 1;
easycap_video_kill_urbs(peasycap);
JOM(8, "returning -EIO\n");
return -EIO;
}
miss++;
}
JOM(8, "first awakening on wq_video after %i waits\n", miss);
rc = field2frame(peasycap);
if (rc)
SAM("ERROR: field2frame() rc = %i\n", rc);
/*---------------------------------------------------------------------------*/
/*
* WAIT FOR THE OTHER FIELD
*/
/*---------------------------------------------------------------------------*/
if (ifield)
ifield = 0;
else
ifield = 1;
miss = 0;
while ((peasycap->field_read == peasycap->field_fill) ||
(0 != (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) ||
(ifield != (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))) {
if (mode)
return -EAGAIN;
JOM(8, "second wait on wq_video %i=field_read %i=field_fill\n",
peasycap->field_read, peasycap->field_fill);
if (0 != (wait_event_interruptible(peasycap->wq_video,
(peasycap->video_idle || peasycap->video_eof ||
((peasycap->field_read != peasycap->field_fill) &&
(0 == (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) &&
(ifield == (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))))))) {
SAM("aborted by signal\n");
return -EIO;
}
if (peasycap->video_idle) {
JOM(8, "%i=peasycap->video_idle returning -EAGAIN\n",
peasycap->video_idle);
return -EAGAIN;
}
if (peasycap->video_eof) {
JOM(8, "%i=peasycap->video_eof\n", peasycap->video_eof);
#if defined(PERSEVERE)
if (1 == peasycap->status) {
JOM(8, "persevering ...\n");
peasycap->video_eof = 0;
peasycap->audio_eof = 0;
if (0 != reset(peasycap)) {
JOM(8, " ... failed returning -EIO\n");
peasycap->video_eof = 1;
peasycap->audio_eof = 1;
easycap_video_kill_urbs(peasycap);
return -EIO;
}
peasycap->status = 0;
JOM(8, " ... OK ... returning -EAGAIN\n");
return -EAGAIN;
}
#endif /*PERSEVERE*/
peasycap->video_eof = 1;
peasycap->audio_eof = 1;
easycap_video_kill_urbs(peasycap);
JOM(8, "returning -EIO\n");
return -EIO;
}
miss++;
}
JOM(8, "second awakening on wq_video after %i waits\n", miss);
rc = field2frame(peasycap);
if (rc)
SAM("ERROR: field2frame() rc = %i\n", rc);
/*---------------------------------------------------------------------------*/
/*
* WASTE THIS FRAME
*/
/*---------------------------------------------------------------------------*/
if (peasycap->skip) {
peasycap->skipped++;
if (peasycap->skip != peasycap->skipped)
return peasycap->skip - peasycap->skipped;
else
peasycap->skipped = 0;
}
/*---------------------------------------------------------------------------*/
peasycap->frame_read = peasycap->frame_fill;
peasycap->queued[peasycap->frame_read] = 0;
peasycap->done[peasycap->frame_read] = V4L2_BUF_FLAG_DONE;
peasycap->frame_fill++;
if (peasycap->frame_buffer_many <= peasycap->frame_fill)
peasycap->frame_fill = 0;
if (0x01 & easycap_standard[peasycap->standard_offset].mask)
peasycap->frame_buffer[peasycap->frame_read][0].kount =
V4L2_FIELD_TOP;
else
peasycap->frame_buffer[peasycap->frame_read][0].kount =
V4L2_FIELD_BOTTOM;
JOM(8, "setting: %i=peasycap->frame_read\n", peasycap->frame_read);
JOM(8, "bumped to: %i=peasycap->frame_fill\n", peasycap->frame_fill);
return 0;
}
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* BY DEFINITION, odd IS true FOR THE FIELD OCCUPYING LINES 1,3,5,...,479
* odd IS false FOR THE FIELD OCCUPYING LINES 0,2,4,...,478
*
* WHEN BOOLEAN PARAMETER decimatepixel IS true, ONLY THE FIELD FOR WHICH
* odd==false IS TRANSFERRED TO THE FRAME BUFFER.
*
*/
/*---------------------------------------------------------------------------*/
static int field2frame(struct easycap *peasycap)
{
void *pex, *pad;
int kex, kad, mex, mad, rex, rad, rad2;
int c2, c3, w2, w3, cz, wz;
int rc, bytesperpixel, multiplier;
int much, more, over, rump, caches, input;
u8 mask, margin;
bool odd, isuy, decimatepixel, badinput;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
badinput = false;
input = 0x07 & peasycap->field_buffer[peasycap->field_read][0].input;
JOM(8, "===== parity %i, input 0x%02X, field buffer %i --> "
"frame buffer %i\n",
peasycap->field_buffer[peasycap->field_read][0].kount,
peasycap->field_buffer[peasycap->field_read][0].input,
peasycap->field_read, peasycap->frame_fill);
JOM(8, "===== %i=bytesperpixel\n", peasycap->bytesperpixel);
/*---------------------------------------------------------------------------*/
/*
* REJECT OR CLEAN BAD FIELDS
*/
/*---------------------------------------------------------------------------*/
if (peasycap->field_read == peasycap->field_fill) {
SAM("ERROR: on entry, still filling field buffer %i\n",
peasycap->field_read);
return 0;
}
#ifdef EASYCAP_TESTCARD
easycap_testcard(peasycap, peasycap->field_read);
#else
if (0 <= input && INPUT_MANY > input) {
if (easycap_bars && VIDEO_LOST_TOLERATE <= peasycap->lost[input])
easycap_testcard(peasycap, peasycap->field_read);
}
#endif /*EASYCAP_TESTCARD*/
/*---------------------------------------------------------------------------*/
bytesperpixel = peasycap->bytesperpixel;
decimatepixel = peasycap->decimatepixel;
if ((2 != bytesperpixel) &&
(3 != bytesperpixel) &&
(4 != bytesperpixel)) {
SAM("MISTAKE: %i=bytesperpixel\n", bytesperpixel);
return -EFAULT;
}
if (decimatepixel)
multiplier = 2;
else
multiplier = 1;
w2 = 2 * multiplier * (peasycap->width);
w3 = bytesperpixel * multiplier * (peasycap->width);
wz = multiplier * (peasycap->height) *
multiplier * (peasycap->width);
kex = peasycap->field_read; mex = 0;
kad = peasycap->frame_fill; mad = 0;
pex = peasycap->field_buffer[kex][0].pgo; rex = PAGE_SIZE;
pad = peasycap->frame_buffer[kad][0].pgo; rad = PAGE_SIZE;
odd = !!(peasycap->field_buffer[kex][0].kount);
if (odd && (!decimatepixel)) {
JOM(8, "initial skipping %4i bytes p.%4i\n",
w3/multiplier, mad);
pad += (w3 / multiplier); rad -= (w3 / multiplier);
}
isuy = true;
mask = 0; rump = 0; caches = 0;
cz = 0;
while (cz < wz) {
/*
* PROCESS ONE LINE OF FRAME AT FULL RESOLUTION:
* READ w2 BYTES FROM FIELD BUFFER,
* WRITE w3 BYTES TO FRAME BUFFER
*/
if (!decimatepixel) {
over = w2;
do {
much = over; more = 0;
margin = 0; mask = 0x00;
if (rex < much)
much = rex;
rump = 0;
if (much % 2) {
SAM("MISTAKE: much is odd\n");
return -EFAULT;
}
more = (bytesperpixel *
much) / 2;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (1 < bytesperpixel) {
if (rad * 2 < much * bytesperpixel) {
/*
* INJUDICIOUS ALTERATION OF
* THIS STATEMENT BLOCK WILL
* CAUSE BREAKAGE. BEWARE.
*/
rad2 = rad + bytesperpixel - 1;
much = ((((2 * rad2)/bytesperpixel)/2) * 2);
rump = ((bytesperpixel * much) / 2) - rad;
more = rad;
}
mask = (u8)rump;
margin = 0;
if (much == rex) {
mask |= 0x04;
if ((mex + 1) < FIELD_BUFFER_SIZE / PAGE_SIZE)
margin = *((u8 *)(peasycap->field_buffer[kex][mex + 1].pgo));
else
mask |= 0x08;
}
} else {
SAM("MISTAKE: %i=bytesperpixel\n",
bytesperpixel);
return -EFAULT;
}
if (rump)
caches++;
if (badinput) {
JOM(8, "ERROR: 0x%02X=->field_buffer"
"[%i][%i].input, "
"0x%02X=(0x08|->input)\n",
peasycap->field_buffer
[kex][mex].input, kex, mex,
(0x08|peasycap->input));
}
rc = redaub(peasycap, pad, pex, much, more,
mask, margin, isuy);
if (0 > rc) {
SAM("ERROR: redaub() failed\n");
return -EFAULT;
}
if (much % 4)
isuy = !isuy;
over -= much; cz += much;
pex += much; rex -= much;
if (!rex) {
mex++;
pex = peasycap->field_buffer[kex][mex].pgo;
rex = PAGE_SIZE;
if (peasycap->field_buffer[kex][mex].input != (0x08|peasycap->input))
badinput = true;
}
pad += more;
rad -= more;
if (!rad) {
mad++;
pad = peasycap->frame_buffer[kad][mad].pgo;
rad = PAGE_SIZE;
if (rump) {
pad += rump;
rad -= rump;
}
}
} while (over);
/*---------------------------------------------------------------------------*/
/*
* SKIP w3 BYTES IN TARGET FRAME BUFFER,
* UNLESS IT IS THE LAST LINE OF AN ODD FRAME
*/
/*---------------------------------------------------------------------------*/
if (!odd || (cz != wz)) {
over = w3;
do {
if (!rad) {
mad++;
pad = peasycap->frame_buffer
[kad][mad].pgo;
rad = PAGE_SIZE;
}
more = over;
if (rad < more)
more = rad;
over -= more;
pad += more;
rad -= more;
} while (over);
}
/*---------------------------------------------------------------------------*/
/*
* PROCESS ONE LINE OF FRAME AT REDUCED RESOLUTION:
* ONLY IF false==odd,
* READ w2 BYTES FROM FIELD BUFFER,
* WRITE w3 / 2 BYTES TO FRAME BUFFER
*/
/*---------------------------------------------------------------------------*/
} else if (!odd) {
over = w2;
do {
much = over; more = 0; margin = 0; mask = 0x00;
if (rex < much)
much = rex;
rump = 0;
if (much % 2) {
SAM("MISTAKE: much is odd\n");
return -EFAULT;
}
more = (bytesperpixel * much) / 4;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (1 < bytesperpixel) {
if (rad * 4 < much * bytesperpixel) {
/*
* INJUDICIOUS ALTERATION OF
* THIS STATEMENT BLOCK
* WILL CAUSE BREAKAGE.
* BEWARE.
*/
rad2 = rad + bytesperpixel - 1;
much = ((((2 * rad2) / bytesperpixel) / 2) * 4);
rump = ((bytesperpixel * much) / 4) - rad;
more = rad;
}
mask = (u8)rump;
margin = 0;
if (much == rex) {
mask |= 0x04;
if ((mex + 1) < FIELD_BUFFER_SIZE / PAGE_SIZE)
margin = *((u8 *)(peasycap->field_buffer[kex][mex + 1].pgo));
else
mask |= 0x08;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
} else {
SAM("MISTAKE: %i=bytesperpixel\n",
bytesperpixel);
return -EFAULT;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (rump)
caches++;
if (badinput) {
JOM(8, "ERROR: 0x%02X=->field_buffer"
"[%i][%i].input, "
"0x%02X=(0x08|->input)\n",
peasycap->field_buffer
[kex][mex].input, kex, mex,
(0x08|peasycap->input));
}
rc = redaub(peasycap, pad, pex, much, more,
mask, margin, isuy);
if (0 > rc) {
SAM("ERROR: redaub() failed\n");
return -EFAULT;
}
over -= much; cz += much;
pex += much; rex -= much;
if (!rex) {
mex++;
pex = peasycap->field_buffer[kex][mex].pgo;
rex = PAGE_SIZE;
if (peasycap->field_buffer[kex][mex].input !=
(0x08|peasycap->input))
badinput = true;
}
pad += more;
rad -= more;
if (!rad) {
mad++;
pad = peasycap->frame_buffer[kad][mad].pgo;
rad = PAGE_SIZE;
if (rump) {
pad += rump;
rad -= rump;
}
}
} while (over);
/*---------------------------------------------------------------------------*/
/*
* OTHERWISE JUST
* READ w2 BYTES FROM FIELD BUFFER AND DISCARD THEM
*/
/*---------------------------------------------------------------------------*/
} else {
over = w2;
do {
if (!rex) {
mex++;
pex = peasycap->field_buffer[kex][mex].pgo;
rex = PAGE_SIZE;
if (peasycap->field_buffer[kex][mex].input !=
(0x08|peasycap->input)) {
JOM(8, "ERROR: 0x%02X=->field_buffer"
"[%i][%i].input, "
"0x%02X=(0x08|->input)\n",
peasycap->field_buffer
[kex][mex].input, kex, mex,
(0x08|peasycap->input));
badinput = true;
}
}
much = over;
if (rex < much)
much = rex;
over -= much;
cz += much;
pex += much;
rex -= much;
} while (over);
}
}
/*---------------------------------------------------------------------------*/
/*
* SANITY CHECKS
*/
/*---------------------------------------------------------------------------*/
c2 = (mex + 1)*PAGE_SIZE - rex;
if (cz != c2)
SAM("ERROR: discrepancy %i in bytes read\n", c2 - cz);
c3 = (mad + 1)*PAGE_SIZE - rad;
if (!decimatepixel) {
if (bytesperpixel * cz != c3)
SAM("ERROR: discrepancy %i in bytes written\n",
c3 - (bytesperpixel * cz));
} else {
if (!odd) {
if (bytesperpixel *
cz != (4 * c3))
SAM("ERROR: discrepancy %i in bytes written\n",
(2*c3)-(bytesperpixel * cz));
} else {
if (0 != c3)
SAM("ERROR: discrepancy %i "
"in bytes written\n", c3);
}
}
if (rump)
SAM("WORRY: undischarged cache at end of line in frame buffer\n");
JOM(8, "===== field2frame(): %i bytes --> %i bytes (incl skip)\n", c2, c3);
JOM(8, "===== field2frame(): %i=mad %i=rad\n", mad, rad);
if (odd)
JOM(8, "+++++ field2frame(): frame buffer %i is full\n", kad);
if (peasycap->field_read == peasycap->field_fill)
SAM("WARNING: on exit, filling field buffer %i\n",
peasycap->field_read);
if (caches)
JOM(8, "%i=caches\n", caches);
return 0;
}
/*---------------------------------------------------------------------------*/
/*
* DECIMATION AND COLOURSPACE CONVERSION.
*
* THIS ROUTINE REQUIRES THAT ALL THE DATA TO BE READ RESIDES ON ONE PAGE
* AND THAT ALL THE DATA TO BE WRITTEN RESIDES ON ONE (DIFFERENT) PAGE.
* THE CALLING ROUTINE MUST ENSURE THAT THIS REQUIREMENT IS MET, AND MUST
* ALSO ENSURE THAT much IS EVEN.
*
* much BYTES ARE READ, AT LEAST (bytesperpixel * much)/2 BYTES ARE WRITTEN
* IF THERE IS NO DECIMATION, HALF THIS AMOUNT IF THERE IS DECIMATION.
*
* mask IS ZERO WHEN NO SPECIAL BEHAVIOUR REQUIRED. OTHERWISE IT IS SET THUS:
* 0x03 & mask = number of bytes to be written to cache instead of to
* frame buffer
* 0x04 & mask => use argument margin to set the chrominance for last pixel
* 0x08 & mask => do not set the chrominance for last pixel
*
* YUV to RGB CONVERSION IS (OR SHOULD BE) ITU-R BT 601.
*
* THERE IS A LOT OF CODE REPETITION IN THIS ROUTINE IN ORDER TO AVOID
* INEFFICIENT SWITCHING INSIDE INNER LOOPS. REARRANGING THE LOGIC TO
* REDUCE CODE LENGTH WILL GENERALLY IMPAIR RUNTIME PERFORMANCE. BEWARE.
*/
/*---------------------------------------------------------------------------*/
static int redaub(struct easycap *peasycap,
void *pad, void *pex, int much, int more,
u8 mask, u8 margin, bool isuy)
{
static s32 ay[256], bu[256], rv[256], gu[256], gv[256];
u8 *pcache;
u8 r, g, b, y, u, v, c, *p2, *p3, *pz, *pr;
int bytesperpixel;
bool byteswaporder, decimatepixel, last;
int j, rump;
s32 tmp;
if (much % 2) {
SAM("MISTAKE: much is odd\n");
return -EFAULT;
}
bytesperpixel = peasycap->bytesperpixel;
byteswaporder = peasycap->byteswaporder;
decimatepixel = peasycap->decimatepixel;
/*---------------------------------------------------------------------------*/
if (!bu[255]) {
for (j = 0; j < 112; j++) {
tmp = (0xFF00 & (453 * j)) >> 8;
bu[j + 128] = tmp; bu[127 - j] = -tmp;
tmp = (0xFF00 & (359 * j)) >> 8;
rv[j + 128] = tmp; rv[127 - j] = -tmp;
tmp = (0xFF00 & (88 * j)) >> 8;
gu[j + 128] = tmp; gu[127 - j] = -tmp;
tmp = (0xFF00 & (183 * j)) >> 8;
gv[j + 128] = tmp; gv[127 - j] = -tmp;
}
for (j = 0; j < 16; j++) {
bu[j] = bu[16]; rv[j] = rv[16];
gu[j] = gu[16]; gv[j] = gv[16];
}
for (j = 240; j < 256; j++) {
bu[j] = bu[239]; rv[j] = rv[239];
gu[j] = gu[239]; gv[j] = gv[239];
}
for (j = 16; j < 236; j++)
ay[j] = j;
for (j = 0; j < 16; j++)
ay[j] = ay[16];
for (j = 236; j < 256; j++)
ay[j] = ay[235];
JOM(8, "lookup tables are prepared\n");
}
pcache = peasycap->pcache;
if (!pcache)
pcache = &peasycap->cache[0];
/*---------------------------------------------------------------------------*/
/*
* TRANSFER CONTENTS OF CACHE TO THE FRAME BUFFER
*/
/*---------------------------------------------------------------------------*/
if (!pcache) {
SAM("MISTAKE: pcache is NULL\n");
return -EFAULT;
}
if (pcache != &peasycap->cache[0])
JOM(16, "cache has %i bytes\n", (int)(pcache - &peasycap->cache[0]));
p2 = &peasycap->cache[0];
p3 = (u8 *)pad - (int)(pcache - &peasycap->cache[0]);
while (p2 < pcache) {
*p3++ = *p2; p2++;
}
pcache = &peasycap->cache[0];
if (p3 != pad) {
SAM("MISTAKE: pointer misalignment\n");
return -EFAULT;
}
/*---------------------------------------------------------------------------*/
rump = (int)(0x03 & mask);
u = 0; v = 0;
p2 = (u8 *)pex; pz = p2 + much; pr = p3 + more; last = false;
p2++;
if (isuy)
u = *(p2 - 1);
else
v = *(p2 - 1);
if (rump)
JOM(16, "%4i=much %4i=more %i=rump\n", much, more, rump);
/*---------------------------------------------------------------------------*/
switch (bytesperpixel) {
case 2: {
if (!decimatepixel) {
memcpy(pad, pex, (size_t)much);
if (!byteswaporder) {
/* UYVY */
return 0;
} else {
/* YUYV */
p3 = (u8 *)pad; pz = p3 + much;
while (pz > p3) {
c = *p3;
*p3 = *(p3 + 1);
*(p3 + 1) = c;
p3 += 2;
}
return 0;
}
} else {
if (!byteswaporder) {
/* UYVY DECIMATED */
p2 = (u8 *)pex; p3 = (u8 *)pad; pz = p2 + much;
while (pz > p2) {
*p3 = *p2;
*(p3 + 1) = *(p2 + 1);
*(p3 + 2) = *(p2 + 2);
*(p3 + 3) = *(p2 + 3);
p3 += 4; p2 += 8;
}
return 0;
} else {
/* YUYV DECIMATED */
p2 = (u8 *)pex; p3 = (u8 *)pad; pz = p2 + much;
while (pz > p2) {
*p3 = *(p2 + 1);
*(p3 + 1) = *p2;
*(p3 + 2) = *(p2 + 3);
*(p3 + 3) = *(p2 + 2);
p3 += 4; p2 += 8;
}
return 0;
}
}
break;
}
case 3:
{
if (!decimatepixel) {
if (!byteswaporder) {
/* RGB */
while (pz > p2) {
if (pr <= (p3 + bytesperpixel))
last = true;
else
last = false;
y = *p2;
if (last && (0x0C & mask)) {
if (0x04 & mask) {
if (isuy)
v = margin;
else
u = margin;
} else
if (0x08 & mask)
;
} else {
if (isuy)
v = *(p2 + 1);
else
u = *(p2 + 1);
}
tmp = ay[(int)y] + rv[(int)v];
r = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] - gu[(int)u] - gv[(int)v];
g = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] + bu[(int)u];
b = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
if (last && rump) {
pcache = &peasycap->cache[0];
switch (bytesperpixel - rump) {
case 1: {
*p3 = r;
*pcache++ = g;
*pcache++ = b;
break;
}
case 2: {
*p3 = r;
*(p3 + 1) = g;
*pcache++ = b;
break;
}
default: {
SAM("MISTAKE: %i=rump\n",
bytesperpixel - rump);
return -EFAULT;
}
}
} else {
*p3 = r;
*(p3 + 1) = g;
*(p3 + 2) = b;
}
p2 += 2;
if (isuy)
isuy = false;
else
isuy = true;
p3 += bytesperpixel;
}
return 0;
} else {
/* BGR */
while (pz > p2) {
if (pr <= (p3 + bytesperpixel))
last = true;
else
last = false;
y = *p2;
if (last && (0x0C & mask)) {
if (0x04 & mask) {
if (isuy)
v = margin;
else
u = margin;
}
else
if (0x08 & mask)
;
} else {
if (isuy)
v = *(p2 + 1);
else
u = *(p2 + 1);
}
tmp = ay[(int)y] + rv[(int)v];
r = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] - gu[(int)u] - gv[(int)v];
g = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] + bu[(int)u];
b = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
if (last && rump) {
pcache = &peasycap->cache[0];
switch (bytesperpixel - rump) {
case 1: {
*p3 = b;
*pcache++ = g;
*pcache++ = r;
break;
}
case 2: {
*p3 = b;
*(p3 + 1) = g;
*pcache++ = r;
break;
}
default: {
SAM("MISTAKE: %i=rump\n",
bytesperpixel - rump);
return -EFAULT;
}
}
} else {
*p3 = b;
*(p3 + 1) = g;
*(p3 + 2) = r;
}
p2 += 2;
if (isuy)
isuy = false;
else
isuy = true;
p3 += bytesperpixel;
}
}
return 0;
} else {
if (!byteswaporder) {
/* RGB DECIMATED */
while (pz > p2) {
if (pr <= (p3 + bytesperpixel))
last = true;
else
last = false;
y = *p2;
if (last && (0x0C & mask)) {
if (0x04 & mask) {
if (isuy)
v = margin;
else
u = margin;
} else
if (0x08 & mask)
;
} else {
if (isuy)
v = *(p2 + 1);
else
u = *(p2 + 1);
}
if (isuy) {
tmp = ay[(int)y] + rv[(int)v];
r = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] - gu[(int)u] -
gv[(int)v];
g = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] + bu[(int)u];
b = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
if (last && rump) {
pcache = &peasycap->cache[0];
switch (bytesperpixel - rump) {
case 1: {
*p3 = r;
*pcache++ = g;
*pcache++ = b;
break;
}
case 2: {
*p3 = r;
*(p3 + 1) = g;
*pcache++ = b;
break;
}
default: {
SAM("MISTAKE: "
"%i=rump\n",
bytesperpixel - rump);
return -EFAULT;
}
}
} else {
*p3 = r;
*(p3 + 1) = g;
*(p3 + 2) = b;
}
isuy = false;
p3 += bytesperpixel;
} else {
isuy = true;
}
p2 += 2;
}
return 0;
} else {
/* BGR DECIMATED */
while (pz > p2) {
if (pr <= (p3 + bytesperpixel))
last = true;
else
last = false;
y = *p2;
if (last && (0x0C & mask)) {
if (0x04 & mask) {
if (isuy)
v = margin;
else
u = margin;
} else
if (0x08 & mask)
;
} else {
if (isuy)
v = *(p2 + 1);
else
u = *(p2 + 1);
}
if (isuy) {
tmp = ay[(int)y] + rv[(int)v];
r = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] - gu[(int)u] -
gv[(int)v];
g = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] + bu[(int)u];
b = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
if (last && rump) {
pcache = &peasycap->cache[0];
switch (bytesperpixel - rump) {
case 1: {
*p3 = b;
*pcache++ = g;
*pcache++ = r;
break;
}
case 2: {
*p3 = b;
*(p3 + 1) = g;
*pcache++ = r;
break;
}
default: {
SAM("MISTAKE: "
"%i=rump\n",
bytesperpixel - rump);
return -EFAULT;
}
}
} else {
*p3 = b;
*(p3 + 1) = g;
*(p3 + 2) = r;
}
isuy = false;
p3 += bytesperpixel;
}
else
isuy = true;
p2 += 2;
}
return 0;
}
}
break;
}
case 4:
{
if (!decimatepixel) {
if (!byteswaporder) {
/* RGBA */
while (pz > p2) {
if (pr <= (p3 + bytesperpixel))
last = true;
else
last = false;
y = *p2;
if (last && (0x0C & mask)) {
if (0x04 & mask) {
if (isuy)
v = margin;
else
u = margin;
} else
if (0x08 & mask)
;
} else {
if (isuy)
v = *(p2 + 1);
else
u = *(p2 + 1);
}
tmp = ay[(int)y] + rv[(int)v];
r = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] - gu[(int)u] - gv[(int)v];
g = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] + bu[(int)u];
b = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
if (last && rump) {
pcache = &peasycap->cache[0];
switch (bytesperpixel - rump) {
case 1: {
*p3 = r;
*pcache++ = g;
*pcache++ = b;
*pcache++ = 0;
break;
}
case 2: {
*p3 = r;
*(p3 + 1) = g;
*pcache++ = b;
*pcache++ = 0;
break;
}
case 3: {
*p3 = r;
*(p3 + 1) = g;
*(p3 + 2) = b;
*pcache++ = 0;
break;
}
default: {
SAM("MISTAKE: %i=rump\n",
bytesperpixel - rump);
return -EFAULT;
}
}
} else {
*p3 = r;
*(p3 + 1) = g;
*(p3 + 2) = b;
*(p3 + 3) = 0;
}
p2 += 2;
if (isuy)
isuy = false;
else
isuy = true;
p3 += bytesperpixel;
}
return 0;
} else {
/*
* BGRA
*/
while (pz > p2) {
if (pr <= (p3 + bytesperpixel))
last = true;
else
last = false;
y = *p2;
if (last && (0x0C & mask)) {
if (0x04 & mask) {
if (isuy)
v = margin;
else
u = margin;
} else
if (0x08 & mask)
;
} else {
if (isuy)
v = *(p2 + 1);
else
u = *(p2 + 1);
}
tmp = ay[(int)y] + rv[(int)v];
r = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] - gu[(int)u] - gv[(int)v];
g = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] + bu[(int)u];
b = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
if (last && rump) {
pcache = &peasycap->cache[0];
switch (bytesperpixel - rump) {
case 1: {
*p3 = b;
*pcache++ = g;
*pcache++ = r;
*pcache++ = 0;
break;
}
case 2: {
*p3 = b;
*(p3 + 1) = g;
*pcache++ = r;
*pcache++ = 0;
break;
}
case 3: {
*p3 = b;
*(p3 + 1) = g;
*(p3 + 2) = r;
*pcache++ = 0;
break;
}
default:
SAM("MISTAKE: %i=rump\n",
bytesperpixel - rump);
return -EFAULT;
}
} else {
*p3 = b;
*(p3 + 1) = g;
*(p3 + 2) = r;
*(p3 + 3) = 0;
}
p2 += 2;
if (isuy)
isuy = false;
else
isuy = true;
p3 += bytesperpixel;
}
}
return 0;
} else {
if (!byteswaporder) {
/*
* RGBA DECIMATED
*/
while (pz > p2) {
if (pr <= (p3 + bytesperpixel))
last = true;
else
last = false;
y = *p2;
if (last && (0x0C & mask)) {
if (0x04 & mask) {
if (isuy)
v = margin;
else
u = margin;
} else
if (0x08 & mask)
;
} else {
if (isuy)
v = *(p2 + 1);
else
u = *(p2 + 1);
}
if (isuy) {
tmp = ay[(int)y] + rv[(int)v];
r = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] - gu[(int)u] -
gv[(int)v];
g = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] + bu[(int)u];
b = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
if (last && rump) {
pcache = &peasycap->cache[0];
switch (bytesperpixel - rump) {
case 1: {
*p3 = r;
*pcache++ = g;
*pcache++ = b;
*pcache++ = 0;
break;
}
case 2: {
*p3 = r;
*(p3 + 1) = g;
*pcache++ = b;
*pcache++ = 0;
break;
}
case 3: {
*p3 = r;
*(p3 + 1) = g;
*(p3 + 2) = b;
*pcache++ = 0;
break;
}
default: {
SAM("MISTAKE: "
"%i=rump\n",
bytesperpixel -
rump);
return -EFAULT;
}
}
} else {
*p3 = r;
*(p3 + 1) = g;
*(p3 + 2) = b;
*(p3 + 3) = 0;
}
isuy = false;
p3 += bytesperpixel;
} else
isuy = true;
p2 += 2;
}
return 0;
} else {
/*
* BGRA DECIMATED
*/
while (pz > p2) {
if (pr <= (p3 + bytesperpixel))
last = true;
else
last = false;
y = *p2;
if (last && (0x0C & mask)) {
if (0x04 & mask) {
if (isuy)
v = margin;
else
u = margin;
} else
if (0x08 & mask)
;
} else {
if (isuy)
v = *(p2 + 1);
else
u = *(p2 + 1);
}
if (isuy) {
tmp = ay[(int)y] + rv[(int)v];
r = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] - gu[(int)u] -
gv[(int)v];
g = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
tmp = ay[(int)y] + bu[(int)u];
b = (255 < tmp) ? 255 : ((0 > tmp) ?
0 : (u8)tmp);
if (last && rump) {
pcache = &peasycap->cache[0];
switch (bytesperpixel - rump) {
case 1: {
*p3 = b;
*pcache++ = g;
*pcache++ = r;
*pcache++ = 0;
break;
}
case 2: {
*p3 = b;
*(p3 + 1) = g;
*pcache++ = r;
*pcache++ = 0;
break;
}
case 3: {
*p3 = b;
*(p3 + 1) = g;
*(p3 + 2) = r;
*pcache++ = 0;
break;
}
default: {
SAM("MISTAKE: "
"%i=rump\n",
bytesperpixel - rump);
return -EFAULT;
}
}
} else {
*p3 = b;
*(p3 + 1) = g;
*(p3 + 2) = r;
*(p3 + 3) = 0;
}
isuy = false;
p3 += bytesperpixel;
} else
isuy = true;
p2 += 2;
}
return 0;
}
}
break;
}
default: {
SAM("MISTAKE: %i=bytesperpixel\n", bytesperpixel);
return -EFAULT;
}
}
return 0;
}
/*****************************************************************************/
/*
* SEE CORBET ET AL. "LINUX DEVICE DRIVERS", 3rd EDITION, PAGES 430-434
*/
/*****************************************************************************/
static void easycap_vma_open(struct vm_area_struct *pvma)
{
struct easycap *peasycap;
peasycap = pvma->vm_private_data;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return;
}
peasycap->vma_many++;
JOT(8, "%i=peasycap->vma_many\n", peasycap->vma_many);
return;
}
/*****************************************************************************/
static void easycap_vma_close(struct vm_area_struct *pvma)
{
struct easycap *peasycap;
peasycap = pvma->vm_private_data;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return;
}
peasycap->vma_many--;
JOT(8, "%i=peasycap->vma_many\n", peasycap->vma_many);
return;
}
/*****************************************************************************/
static int easycap_vma_fault(struct vm_area_struct *pvma, struct vm_fault *pvmf)
{
int k, m, retcode;
void *pbuf;
struct page *page;
struct easycap *peasycap;
retcode = VM_FAULT_NOPAGE;
if (!pvma) {
SAY("pvma is NULL\n");
return retcode;
}
if (!pvmf) {
SAY("pvmf is NULL\n");
return retcode;
}
k = (pvmf->pgoff) / (FRAME_BUFFER_SIZE/PAGE_SIZE);
m = (pvmf->pgoff) % (FRAME_BUFFER_SIZE/PAGE_SIZE);
if (!m)
JOT(4, "%4i=k, %4i=m\n", k, m);
else
JOT(16, "%4i=k, %4i=m\n", k, m);
if ((0 > k) || (FRAME_BUFFER_MANY <= k)) {
SAY("ERROR: buffer index %i out of range\n", k);
return retcode;
}
if ((0 > m) || (FRAME_BUFFER_SIZE/PAGE_SIZE <= m)) {
SAY("ERROR: page number %i out of range\n", m);
return retcode;
}
peasycap = pvma->vm_private_data;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return retcode;
}
/*---------------------------------------------------------------------------*/
pbuf = peasycap->frame_buffer[k][m].pgo;
if (!pbuf) {
SAM("ERROR: pbuf is NULL\n");
return retcode;
}
page = virt_to_page(pbuf);
if (!page) {
SAM("ERROR: page is NULL\n");
return retcode;
}
get_page(page);
/*---------------------------------------------------------------------------*/
if (!page) {
SAM("ERROR: page is NULL after get_page(page)\n");
} else {
pvmf->page = page;
retcode = VM_FAULT_MINOR;
}
return retcode;
}
static const struct vm_operations_struct easycap_vm_ops = {
.open = easycap_vma_open,
.close = easycap_vma_close,
.fault = easycap_vma_fault,
};
static int easycap_mmap(struct file *file, struct vm_area_struct *pvma)
{
JOT(8, "\n");
pvma->vm_ops = &easycap_vm_ops;
pvma->vm_flags |= VM_RESERVED;
if (file)
pvma->vm_private_data = file->private_data;
easycap_vma_open(pvma);
return 0;
}
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* ON COMPLETION OF A VIDEO URB ITS DATA IS COPIED TO THE FIELD BUFFERS
* PROVIDED peasycap->video_idle IS ZERO. REGARDLESS OF THIS BEING TRUE,
* IT IS RESUBMITTED PROVIDED peasycap->video_isoc_streaming IS NOT ZERO.
*
* THIS FUNCTION IS AN INTERRUPT SERVICE ROUTINE AND MUST NOT SLEEP.
*
* INFORMATION ABOUT THE VALIDITY OF THE CONTENTS OF THE FIELD BUFFER ARE
* STORED IN THE TWO-BYTE STATUS PARAMETER
* peasycap->field_buffer[peasycap->field_fill][0].kount
* NOTICE THAT THE INFORMATION IS STORED ONLY WITH PAGE 0 OF THE FIELD BUFFER.
*
* THE LOWER BYTE CONTAINS THE FIELD PARITY BYTE FURNISHED BY THE SAA7113H
* CHIP.
*
* THE UPPER BYTE IS ZERO IF NO PROBLEMS, OTHERWISE:
* 0 != (kount & 0x8000) => AT LEAST ONE URB COMPLETED WITH ERRORS
* 0 != (kount & 0x4000) => BUFFER HAS TOO MUCH DATA
* 0 != (kount & 0x2000) => BUFFER HAS NOT ENOUGH DATA
* 0 != (kount & 0x1000) => BUFFER HAS DATA FROM DISPARATE INPUTS
* 0 != (kount & 0x0400) => RESERVED
* 0 != (kount & 0x0200) => FIELD BUFFER NOT YET CHECKED
* 0 != (kount & 0x0100) => BUFFER HAS TWO EXTRA BYTES - WHY?
*/
/*---------------------------------------------------------------------------*/
static void easycap_complete(struct urb *purb)
{
struct easycap *peasycap;
struct data_buffer *pfield_buffer;
char errbuf[16];
int i, more, much, leap, rc, last;
int videofieldamount;
unsigned int override, bad;
int framestatus, framelength, frameactual, frameoffset;
u8 *pu;
if (!purb) {
SAY("ERROR: easycap_complete(): purb is NULL\n");
return;
}
peasycap = purb->context;
if (!peasycap) {
SAY("ERROR: easycap_complete(): peasycap is NULL\n");
return;
}
if (peasycap->video_eof)
return;
for (i = 0; i < VIDEO_ISOC_BUFFER_MANY; i++)
if (purb->transfer_buffer == peasycap->video_isoc_buffer[i].pgo)
break;
JOM(16, "%2i=urb\n", i);
last = peasycap->video_isoc_sequence;
if ((((VIDEO_ISOC_BUFFER_MANY - 1) == last) && (0 != i)) ||
(((VIDEO_ISOC_BUFFER_MANY - 1) != last) && ((last + 1) != i))) {
JOM(16, "ERROR: out-of-order urbs %i,%i ... continuing\n",
last, i);
}
peasycap->video_isoc_sequence = i;
if (peasycap->video_idle) {
JOM(16, "%i=video_idle %i=video_isoc_streaming\n",
peasycap->video_idle, peasycap->video_isoc_streaming);
if (peasycap->video_isoc_streaming) {
rc = usb_submit_urb(purb, GFP_ATOMIC);
if (rc) {
SAM("%s:%d ENOMEM\n", strerror(rc), rc);
if (-ENODEV != rc)
SAM("ERROR: while %i=video_idle, "
"usb_submit_urb() "
"failed with rc:\n",
peasycap->video_idle);
}
}
return;
}
override = 0;
/*---------------------------------------------------------------------------*/
if (FIELD_BUFFER_MANY <= peasycap->field_fill) {
SAM("ERROR: bad peasycap->field_fill\n");
return;
}
if (purb->status) {
if ((-ESHUTDOWN == purb->status) || (-ENOENT == purb->status)) {
JOM(8, "urb status -ESHUTDOWN or -ENOENT\n");
return;
}
(peasycap->field_buffer[peasycap->field_fill][0].kount) |= 0x8000 ;
SAM("ERROR: bad urb status -%s: %d\n",
strerror(purb->status), purb->status);
/*---------------------------------------------------------------------------*/
} else {
for (i = 0; i < purb->number_of_packets; i++) {
if (0 != purb->iso_frame_desc[i].status) {
(peasycap->field_buffer
[peasycap->field_fill][0].kount) |= 0x8000 ;
/* FIXME: 1. missing '-' check boundaries */
strcpy(&errbuf[0],
strerror(purb->iso_frame_desc[i].status));
}
framestatus = purb->iso_frame_desc[i].status;
framelength = purb->iso_frame_desc[i].length;
frameactual = purb->iso_frame_desc[i].actual_length;
frameoffset = purb->iso_frame_desc[i].offset;
JOM(16, "frame[%2i]:"
"%4i=status "
"%4i=actual "
"%4i=length "
"%5i=offset\n",
i, framestatus, frameactual, framelength, frameoffset);
if (!purb->iso_frame_desc[i].status) {
more = purb->iso_frame_desc[i].actual_length;
pfield_buffer = &peasycap->field_buffer
[peasycap->field_fill][peasycap->field_page];
videofieldamount = (peasycap->field_page *
PAGE_SIZE) +
(int)(pfield_buffer->pto - pfield_buffer->pgo);
if (4 == more)
peasycap->video_mt++;
if (4 < more) {
if (peasycap->video_mt) {
JOM(8, "%4i empty video urb frames\n",
peasycap->video_mt);
peasycap->video_mt = 0;
}
if (FIELD_BUFFER_MANY <= peasycap->field_fill) {
SAM("ERROR: bad peasycap->field_fill\n");
return;
}
if (FIELD_BUFFER_SIZE/PAGE_SIZE <=
peasycap->field_page) {
SAM("ERROR: bad peasycap->field_page\n");
return;
}
pfield_buffer = &peasycap->field_buffer
[peasycap->field_fill][peasycap->field_page];
pu = (u8 *)(purb->transfer_buffer +
purb->iso_frame_desc[i].offset);
if (0x80 & *pu)
leap = 8;
else
leap = 4;
/*--------------------------------------------------------------------------*/
/*
* EIGHT-BYTE END-OF-VIDEOFIELD MARKER.
* NOTE: A SUCCESSION OF URB FRAMES FOLLOWING THIS ARE EMPTY,
* CORRESPONDING TO THE FIELD FLYBACK (VERTICAL BLANKING) PERIOD.
*
* PROVIDED THE FIELD BUFFER CONTAINS GOOD DATA AS INDICATED BY A ZERO UPPER
* BYTE OF
* peasycap->field_buffer[peasycap->field_fill][0].kount
* THE CONTENTS OF THE FIELD BUFFER ARE OFFERED TO dqbuf(), field_read IS
* UPDATED AND field_fill IS BUMPED. IF THE FIELD BUFFER CONTAINS BAD DATA
* NOTHING IS OFFERED TO dqbuf().
*
* THE DECISION ON WHETHER THE PARITY OF THE OFFERED FIELD BUFFER IS RIGHT
* RESTS WITH dqbuf().
*/
/*---------------------------------------------------------------------------*/
if ((8 == more) || override) {
if (videofieldamount >
peasycap->videofieldamount) {
if (2 == videofieldamount -
peasycap->
videofieldamount) {
(peasycap->field_buffer
[peasycap->field_fill]
[0].kount) |= 0x0100;
peasycap->video_junk += (1 +
VIDEO_JUNK_TOLERATE);
} else
(peasycap->field_buffer
[peasycap->field_fill]
[0].kount) |= 0x4000;
} else if (videofieldamount <
peasycap->
videofieldamount) {
(peasycap->field_buffer
[peasycap->field_fill]
[0].kount) |= 0x2000;
}
bad = 0xFF00 & peasycap->field_buffer
[peasycap->field_fill]
[0].kount;
if (!bad) {
(peasycap->video_junk)--;
if (-VIDEO_JUNK_TOLERATE >
peasycap->video_junk)
peasycap->video_junk =
-VIDEO_JUNK_TOLERATE;
peasycap->field_read =
(peasycap->
field_fill)++;
if (FIELD_BUFFER_MANY <=
peasycap->
field_fill)
peasycap->
field_fill = 0;
peasycap->field_page = 0;
pfield_buffer = &peasycap->
field_buffer
[peasycap->
field_fill]
[peasycap->
field_page];
pfield_buffer->pto =
pfield_buffer->pgo;
JOM(8, "bumped to: %i="
"peasycap->"
"field_fill %i="
"parity\n",
peasycap->field_fill,
0x00FF &
pfield_buffer->kount);
JOM(8, "field buffer %i has "
"%i bytes fit to be "
"read\n",
peasycap->field_read,
videofieldamount);
JOM(8, "wakeup call to "
"wq_video, "
"%i=field_read "
"%i=field_fill "
"%i=parity\n",
peasycap->field_read,
peasycap->field_fill,
0x00FF & peasycap->
field_buffer
[peasycap->
field_read][0].kount);
wake_up_interruptible
(&(peasycap->
wq_video));
} else {
peasycap->video_junk++;
if (bad & 0x0010)
peasycap->video_junk +=
(1 + VIDEO_JUNK_TOLERATE/2);
JOM(8, "field buffer %i had %i "
"bytes, now discarded: "
"0x%04X\n",
peasycap->field_fill,
videofieldamount,
(0xFF00 &
peasycap->field_buffer
[peasycap->field_fill][0].
kount));
(peasycap->field_fill)++;
if (FIELD_BUFFER_MANY <=
peasycap->field_fill)
peasycap->field_fill = 0;
peasycap->field_page = 0;
pfield_buffer =
&peasycap->field_buffer
[peasycap->field_fill]
[peasycap->field_page];
pfield_buffer->pto =
pfield_buffer->pgo;
JOM(8, "bumped to: %i=peasycap->"
"field_fill %i=parity\n",
peasycap->field_fill,
0x00FF & pfield_buffer->kount);
}
if (8 == more) {
JOM(8, "end-of-field: received "
"parity byte 0x%02X\n",
(0xFF & *pu));
if (0x40 & *pu)
pfield_buffer->kount = 0x0000;
else
pfield_buffer->kount = 0x0001;
pfield_buffer->input = 0x08 |
(0x07 & peasycap->input);
JOM(8, "end-of-field: 0x%02X=kount\n",
0xFF & pfield_buffer->kount);
}
}
/*---------------------------------------------------------------------------*/
/*
* COPY more BYTES FROM ISOC BUFFER TO FIELD BUFFER
*/
/*---------------------------------------------------------------------------*/
pu += leap;
more -= leap;
if (FIELD_BUFFER_MANY <= peasycap->field_fill) {
SAM("ERROR: bad peasycap->field_fill\n");
return;
}
if (FIELD_BUFFER_SIZE/PAGE_SIZE <= peasycap->field_page) {
SAM("ERROR: bad peasycap->field_page\n");
return;
}
pfield_buffer = &peasycap->field_buffer
[peasycap->field_fill][peasycap->field_page];
while (more) {
pfield_buffer = &peasycap->field_buffer
[peasycap->field_fill]
[peasycap->field_page];
if (PAGE_SIZE < (pfield_buffer->pto -
pfield_buffer->pgo)) {
SAM("ERROR: bad pfield_buffer->pto\n");
return;
}
if (PAGE_SIZE == (pfield_buffer->pto -
pfield_buffer->pgo)) {
(peasycap->field_page)++;
if (FIELD_BUFFER_SIZE/PAGE_SIZE <=
peasycap->field_page) {
JOM(16, "wrapping peasycap->"
"field_page\n");
peasycap->field_page = 0;
}
pfield_buffer = &peasycap->
field_buffer
[peasycap->field_fill]
[peasycap->field_page];
pfield_buffer->pto = pfield_buffer->pgo;
pfield_buffer->input = 0x08 |
(0x07 & peasycap->input);
if ((peasycap->field_buffer[peasycap->
field_fill][0]).
input !=
pfield_buffer->input)
(peasycap->field_buffer
[peasycap->field_fill]
[0]).kount |= 0x1000;
}
much = PAGE_SIZE -
(int)(pfield_buffer->pto -
pfield_buffer->pgo);
if (much > more)
much = more;
memcpy(pfield_buffer->pto, pu, much);
pu += much;
(pfield_buffer->pto) += much;
more -= much;
}
}
}
}
}
/*---------------------------------------------------------------------------*/
/*
* RESUBMIT THIS URB, UNLESS A SEVERE PERSISTENT ERROR CONDITION EXISTS.
*
* IF THE WAIT QUEUES ARE NOT CLEARED IN RESPONSE TO AN ERROR CONDITION
* THE USERSPACE PROGRAM, E.G. mplayer, MAY HANG ON EXIT. BEWARE.
*/
/*---------------------------------------------------------------------------*/
if (VIDEO_ISOC_BUFFER_MANY <= peasycap->video_junk) {
SAM("easycap driver shutting down on condition green\n");
peasycap->status = 1;
peasycap->video_eof = 1;
peasycap->video_junk = 0;
wake_up_interruptible(&peasycap->wq_video);
#if !defined(PERSEVERE)
peasycap->audio_eof = 1;
wake_up_interruptible(&peasycap->wq_audio);
#endif /*PERSEVERE*/
return;
}
if (peasycap->video_isoc_streaming) {
rc = usb_submit_urb(purb, GFP_ATOMIC);
if (rc) {
SAM("%s: %d\n", strerror(rc), rc);
if (-ENODEV != rc)
SAM("ERROR: while %i=video_idle, "
"usb_submit_urb() "
"failed with rc:\n",
peasycap->video_idle);
}
}
return;
}
static struct easycap *alloc_easycap(u8 bInterfaceNumber)
{
struct easycap *peasycap;
int i;
peasycap = kzalloc(sizeof(struct easycap), GFP_KERNEL);
if (!peasycap) {
SAY("ERROR: Could not allocate peasycap\n");
return NULL;
}
if (mutex_lock_interruptible(&mutex_dongle)) {
SAY("ERROR: cannot lock mutex_dongle\n");
kfree(peasycap);
return NULL;
}
/* Find a free dongle in easycapdc60_dongle array */
for (i = 0; i < DONGLE_MANY; i++) {
if ((!easycapdc60_dongle[i].peasycap) &&
(!mutex_is_locked(&easycapdc60_dongle[i].mutex_video)) &&
(!mutex_is_locked(&easycapdc60_dongle[i].mutex_audio))) {
easycapdc60_dongle[i].peasycap = peasycap;
peasycap->isdongle = i;
JOM(8, "intf[%i]: peasycap-->easycap"
"_dongle[%i].peasycap\n",
bInterfaceNumber, i);
break;
}
}
mutex_unlock(&mutex_dongle);
if (i >= DONGLE_MANY) {
SAM("ERROR: too many dongles\n");
kfree(peasycap);
return NULL;
}
return peasycap;
}
static void free_easycap(struct easycap *peasycap)
{
int allocation_video_urb;
int allocation_video_page;
int allocation_video_struct;
int allocation_audio_urb;
int allocation_audio_page;
int allocation_audio_struct;
int registered_video, registered_audio;
int kd;
JOM(4, "freeing easycap structure.\n");
allocation_video_urb = peasycap->allocation_video_urb;
allocation_video_page = peasycap->allocation_video_page;
allocation_video_struct = peasycap->allocation_video_struct;
registered_video = peasycap->registered_video;
allocation_audio_urb = peasycap->allocation_audio_urb;
allocation_audio_page = peasycap->allocation_audio_page;
allocation_audio_struct = peasycap->allocation_audio_struct;
registered_audio = peasycap->registered_audio;
kd = easycap_isdongle(peasycap);
if (0 <= kd && DONGLE_MANY > kd) {
if (mutex_lock_interruptible(&mutex_dongle)) {
SAY("ERROR: cannot down mutex_dongle\n");
} else {
JOM(4, "locked mutex_dongle\n");
easycapdc60_dongle[kd].peasycap = NULL;
mutex_unlock(&mutex_dongle);
JOM(4, "unlocked mutex_dongle\n");
JOT(4, " null-->dongle[%i].peasycap\n", kd);
allocation_video_struct -= sizeof(struct easycap);
}
} else {
SAY("ERROR: cannot purge dongle[].peasycap");
}
/* Free device structure */
kfree(peasycap);
SAY("%8i=video urbs after all deletions\n", allocation_video_urb);
SAY("%8i=video pages after all deletions\n", allocation_video_page);
SAY("%8i=video structs after all deletions\n", allocation_video_struct);
SAY("%8i=video devices after all deletions\n", registered_video);
SAY("%8i=audio urbs after all deletions\n", allocation_audio_urb);
SAY("%8i=audio pages after all deletions\n", allocation_audio_page);
SAY("%8i=audio structs after all deletions\n", allocation_audio_struct);
SAY("%8i=audio devices after all deletions\n", registered_audio);
}
/*
* FIXME: Identify the appropriate pointer peasycap for interfaces
* 1 and 2. The address of peasycap->pusb_device is reluctantly used
* for this purpose.
*/
static struct easycap *get_easycap(struct usb_device *usbdev,
u8 bInterfaceNumber)
{
int i;
struct easycap *peasycap;
for (i = 0; i < DONGLE_MANY; i++) {
if (easycapdc60_dongle[i].peasycap->pusb_device == usbdev) {
peasycap = easycapdc60_dongle[i].peasycap;
JOT(8, "intf[%i]: dongle[%i].peasycap\n",
bInterfaceNumber, i);
break;
}
}
if (i >= DONGLE_MANY) {
SAY("ERROR: peasycap is unknown when probing interface %i\n",
bInterfaceNumber);
return NULL;
}
if (!peasycap) {
SAY("ERROR: peasycap is NULL when probing interface %i\n",
bInterfaceNumber);
return NULL;
}
return peasycap;
}
static void init_easycap(struct easycap *peasycap,
struct usb_device *usbdev,
struct usb_interface *intf,
u8 bInterfaceNumber)
{
/* Save usb_device and usb_interface */
peasycap->pusb_device = usbdev;
peasycap->pusb_interface = intf;
peasycap->minor = -1;
kref_init(&peasycap->kref);
JOM(8, "intf[%i]: after kref_init(..._video) "
"%i=peasycap->kref.refcount.counter\n",
bInterfaceNumber, peasycap->kref.refcount.counter);
/* module params */
peasycap->gain = (s8)clamp(easycap_gain, 0, 31);
init_waitqueue_head(&peasycap->wq_video);
init_waitqueue_head(&peasycap->wq_audio);
init_waitqueue_head(&peasycap->wq_trigger);
peasycap->allocation_video_struct = sizeof(struct easycap);
peasycap->microphone = false;
peasycap->video_interface = -1;
peasycap->video_altsetting_on = -1;
peasycap->video_altsetting_off = -1;
peasycap->video_endpointnumber = -1;
peasycap->video_isoc_maxframesize = -1;
peasycap->video_isoc_buffer_size = -1;
peasycap->audio_interface = -1;
peasycap->audio_altsetting_on = -1;
peasycap->audio_altsetting_off = -1;
peasycap->audio_endpointnumber = -1;
peasycap->audio_isoc_maxframesize = -1;
peasycap->audio_isoc_buffer_size = -1;
peasycap->frame_buffer_many = FRAME_BUFFER_MANY;
peasycap->ntsc = easycap_ntsc;
JOM(8, "defaulting initially to %s\n",
easycap_ntsc ? "NTSC" : "PAL");
}
static int populate_inputset(struct easycap *peasycap)
{
struct inputset *inputset;
struct easycap_format *peasycap_format;
struct v4l2_pix_format *pix;
int m, i, k, mask, fmtidx;
s32 value;
inputset = peasycap->inputset;
fmtidx = peasycap->ntsc ? NTSC_M : PAL_BGHIN;
m = 0;
mask = 0;
for (i = 0; easycap_standard[i].mask != 0xffff; i++) {
if (fmtidx == easycap_standard[i].v4l2_standard.index) {
m++;
for (k = 0; k < INPUT_MANY; k++)
inputset[k].standard_offset = i;
mask = easycap_standard[i].mask;
}
}
if (m != 1) {
SAM("ERROR: inputset->standard_offset unpopulated, %i=m\n", m);
return -ENOENT;
}
peasycap_format = &easycap_format[0];
m = 0;
for (i = 0; peasycap_format->v4l2_format.fmt.pix.width; i++) {
pix = &peasycap_format->v4l2_format.fmt.pix;
if (((peasycap_format->mask & 0x0F) == (mask & 0x0F))
&& pix->field == V4L2_FIELD_NONE
&& pix->pixelformat == V4L2_PIX_FMT_UYVY
&& pix->width == 640 && pix->height == 480) {
m++;
for (k = 0; k < INPUT_MANY; k++)
inputset[k].format_offset = i;
break;
}
peasycap_format++;
}
if (m != 1) {
SAM("ERROR: inputset[]->format_offset unpopulated\n");
return -ENOENT;
}
m = 0;
for (i = 0; easycap_control[i].id != 0xffffffff; i++) {
value = easycap_control[i].default_value;
if (V4L2_CID_BRIGHTNESS == easycap_control[i].id) {
m++;
for (k = 0; k < INPUT_MANY; k++)
inputset[k].brightness = value;
} else if (V4L2_CID_CONTRAST == easycap_control[i].id) {
m++;
for (k = 0; k < INPUT_MANY; k++)
inputset[k].contrast = value;
} else if (V4L2_CID_SATURATION == easycap_control[i].id) {
m++;
for (k = 0; k < INPUT_MANY; k++)
inputset[k].saturation = value;
} else if (V4L2_CID_HUE == easycap_control[i].id) {
m++;
for (k = 0; k < INPUT_MANY; k++)
inputset[k].hue = value;
}
}
if (m != 4) {
SAM("ERROR: inputset[]->brightness underpopulated\n");
return -ENOENT;
}
for (k = 0; k < INPUT_MANY; k++)
inputset[k].input = k;
JOM(4, "populated inputset[]\n");
return 0;
}
static int alloc_framebuffers(struct easycap *peasycap)
{
int i, j;
void *pbuf;
JOM(4, "allocating %i frame buffers of size %li\n",
FRAME_BUFFER_MANY, (long int)FRAME_BUFFER_SIZE);
JOM(4, ".... each scattered over %li pages\n",
FRAME_BUFFER_SIZE/PAGE_SIZE);
for (i = 0; i < FRAME_BUFFER_MANY; i++) {
for (j = 0; j < FRAME_BUFFER_SIZE/PAGE_SIZE; j++) {
if (peasycap->frame_buffer[i][j].pgo)
SAM("attempting to reallocate framebuffers\n");
else {
pbuf = (void *)__get_free_page(GFP_KERNEL);
if (!pbuf) {
SAM("ERROR: Could not allocate "
"framebuffer %i page %i\n", i, j);
return -ENOMEM;
}
peasycap->allocation_video_page += 1;
peasycap->frame_buffer[i][j].pgo = pbuf;
}
peasycap->frame_buffer[i][j].pto =
peasycap->frame_buffer[i][j].pgo;
}
}
peasycap->frame_fill = 0;
peasycap->frame_read = 0;
JOM(4, "allocation of frame buffers done: %i pages\n", i*j);
return 0;
}
static void free_framebuffers(struct easycap *peasycap)
{
int k, m, gone;
JOM(4, "freeing video frame buffers.\n");
gone = 0;
for (k = 0; k < FRAME_BUFFER_MANY; k++) {
for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++) {
if (peasycap->frame_buffer[k][m].pgo) {
free_page((unsigned long)
peasycap->frame_buffer[k][m].pgo);
peasycap->frame_buffer[k][m].pgo = NULL;
peasycap->allocation_video_page -= 1;
gone++;
}
}
}
JOM(4, "video frame buffers freed: %i pages\n", gone);
}
static int alloc_fieldbuffers(struct easycap *peasycap)
{
int i, j;
void *pbuf;
JOM(4, "allocating %i field buffers of size %li\n",
FIELD_BUFFER_MANY, (long int)FIELD_BUFFER_SIZE);
JOM(4, ".... each scattered over %li pages\n",
FIELD_BUFFER_SIZE/PAGE_SIZE);
for (i = 0; i < FIELD_BUFFER_MANY; i++) {
for (j = 0; j < FIELD_BUFFER_SIZE/PAGE_SIZE; j++) {
if (peasycap->field_buffer[i][j].pgo) {
SAM("ERROR: attempting to reallocate "
"fieldbuffers\n");
} else {
pbuf = (void *) __get_free_page(GFP_KERNEL);
if (!pbuf) {
SAM("ERROR: Could not allocate "
"fieldbuffer %i page %i\n", i, j);
return -ENOMEM;
}
peasycap->allocation_video_page += 1;
peasycap->field_buffer[i][j].pgo = pbuf;
}
peasycap->field_buffer[i][j].pto =
peasycap->field_buffer[i][j].pgo;
}
/* TODO: Hardcoded 0x0200 meaning? */
peasycap->field_buffer[i][0].kount = 0x0200;
}
peasycap->field_fill = 0;
peasycap->field_page = 0;
peasycap->field_read = 0;
JOM(4, "allocation of field buffers done: %i pages\n", i*j);
return 0;
}
static void free_fieldbuffers(struct easycap *peasycap)
{
int k, m, gone;
JOM(4, "freeing video field buffers.\n");
gone = 0;
for (k = 0; k < FIELD_BUFFER_MANY; k++) {
for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++) {
if (peasycap->field_buffer[k][m].pgo) {
free_page((unsigned long)
peasycap->field_buffer[k][m].pgo);
peasycap->field_buffer[k][m].pgo = NULL;
peasycap->allocation_video_page -= 1;
gone++;
}
}
}
JOM(4, "video field buffers freed: %i pages\n", gone);
}
static int alloc_isocbuffers(struct easycap *peasycap)
{
int i;
void *pbuf;
JOM(4, "allocating %i isoc video buffers of size %i\n",
VIDEO_ISOC_BUFFER_MANY,
peasycap->video_isoc_buffer_size);
JOM(4, ".... each occupying contiguous memory pages\n");
for (i = 0; i < VIDEO_ISOC_BUFFER_MANY; i++) {
pbuf = (void *)__get_free_pages(GFP_KERNEL,
VIDEO_ISOC_ORDER);
if (!pbuf) {
SAM("ERROR: Could not allocate isoc "
"video buffer %i\n", i);
return -ENOMEM;
}
peasycap->allocation_video_page += BIT(VIDEO_ISOC_ORDER);
peasycap->video_isoc_buffer[i].pgo = pbuf;
peasycap->video_isoc_buffer[i].pto =
pbuf + peasycap->video_isoc_buffer_size;
peasycap->video_isoc_buffer[i].kount = i;
}
JOM(4, "allocation of isoc video buffers done: %i pages\n",
i * (0x01 << VIDEO_ISOC_ORDER));
return 0;
}
static void free_isocbuffers(struct easycap *peasycap)
{
int k, m;
JOM(4, "freeing video isoc buffers.\n");
m = 0;
for (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) {
if (peasycap->video_isoc_buffer[k].pgo) {
free_pages((unsigned long)
peasycap->video_isoc_buffer[k].pgo,
VIDEO_ISOC_ORDER);
peasycap->video_isoc_buffer[k].pgo = NULL;
peasycap->allocation_video_page -=
BIT(VIDEO_ISOC_ORDER);
m++;
}
}
JOM(4, "isoc video buffers freed: %i pages\n",
m * (0x01 << VIDEO_ISOC_ORDER));
}
static int create_video_urbs(struct easycap *peasycap)
{
struct urb *purb;
struct data_urb *pdata_urb;
int i, j;
JOM(4, "allocating %i struct urb.\n", VIDEO_ISOC_BUFFER_MANY);
JOM(4, "using %i=peasycap->video_isoc_framesperdesc\n",
peasycap->video_isoc_framesperdesc);
JOM(4, "using %i=peasycap->video_isoc_maxframesize\n",
peasycap->video_isoc_maxframesize);
JOM(4, "using %i=peasycap->video_isoc_buffer_sizen",
peasycap->video_isoc_buffer_size);
for (i = 0; i < VIDEO_ISOC_BUFFER_MANY; i++) {
purb = usb_alloc_urb(peasycap->video_isoc_framesperdesc,
GFP_KERNEL);
if (!purb) {
SAM("ERROR: usb_alloc_urb returned NULL for buffer "
"%i\n", i);
return -ENOMEM;
}
peasycap->allocation_video_urb += 1;
pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL);
if (!pdata_urb) {
usb_free_urb(purb);
SAM("ERROR: Could not allocate struct data_urb.\n");
return -ENOMEM;
}
peasycap->allocation_video_struct +=
sizeof(struct data_urb);
pdata_urb->purb = purb;
pdata_urb->isbuf = i;
pdata_urb->length = 0;
list_add_tail(&(pdata_urb->list_head),
peasycap->purb_video_head);
if (!i) {
JOM(4, "initializing video urbs thus:\n");
JOM(4, " purb->interval = 1;\n");
JOM(4, " purb->dev = peasycap->pusb_device;\n");
JOM(4, " purb->pipe = usb_rcvisocpipe"
"(peasycap->pusb_device,%i);\n",
peasycap->video_endpointnumber);
JOM(4, " purb->transfer_flags = URB_ISO_ASAP;\n");
JOM(4, " purb->transfer_buffer = peasycap->"
"video_isoc_buffer[.].pgo;\n");
JOM(4, " purb->transfer_buffer_length = %i;\n",
peasycap->video_isoc_buffer_size);
JOM(4, " purb->complete = easycap_complete;\n");
JOM(4, " purb->context = peasycap;\n");
JOM(4, " purb->start_frame = 0;\n");
JOM(4, " purb->number_of_packets = %i;\n",
peasycap->video_isoc_framesperdesc);
JOM(4, " for (j = 0; j < %i; j++)\n",
peasycap->video_isoc_framesperdesc);
JOM(4, " {\n");
JOM(4, " purb->iso_frame_desc[j].offset = j*%i;\n",
peasycap->video_isoc_maxframesize);
JOM(4, " purb->iso_frame_desc[j].length = %i;\n",
peasycap->video_isoc_maxframesize);
JOM(4, " }\n");
}
purb->interval = 1;
purb->dev = peasycap->pusb_device;
purb->pipe = usb_rcvisocpipe(peasycap->pusb_device,
peasycap->video_endpointnumber);
purb->transfer_flags = URB_ISO_ASAP;
purb->transfer_buffer = peasycap->video_isoc_buffer[i].pgo;
purb->transfer_buffer_length =
peasycap->video_isoc_buffer_size;
purb->complete = easycap_complete;
purb->context = peasycap;
purb->start_frame = 0;
purb->number_of_packets = peasycap->video_isoc_framesperdesc;
for (j = 0; j < peasycap->video_isoc_framesperdesc; j++) {
purb->iso_frame_desc[j].offset =
j * peasycap->video_isoc_maxframesize;
purb->iso_frame_desc[j].length =
peasycap->video_isoc_maxframesize;
}
}
JOM(4, "allocation of %i struct urb done.\n", i);
return 0;
}
static void free_video_urbs(struct easycap *peasycap)
{
struct list_head *plist_head, *plist_next;
struct data_urb *pdata_urb;
int m;
if (peasycap->purb_video_head) {
m = 0;
list_for_each(plist_head, peasycap->purb_video_head) {
pdata_urb = list_entry(plist_head,
struct data_urb, list_head);
if (pdata_urb && pdata_urb->purb) {
usb_free_urb(pdata_urb->purb);
pdata_urb->purb = NULL;
peasycap->allocation_video_urb--;
m++;
}
}
JOM(4, "%i video urbs freed\n", m);
JOM(4, "freeing video data_urb structures.\n");
m = 0;
list_for_each_safe(plist_head, plist_next,
peasycap->purb_video_head) {
pdata_urb = list_entry(plist_head,
struct data_urb, list_head);
if (pdata_urb) {
peasycap->allocation_video_struct -=
sizeof(struct data_urb);
kfree(pdata_urb);
m++;
}
}
JOM(4, "%i video data_urb structures freed\n", m);
JOM(4, "setting peasycap->purb_video_head=NULL\n");
peasycap->purb_video_head = NULL;
}
}
static int alloc_audio_buffers(struct easycap *peasycap)
{
void *pbuf;
int k;
JOM(4, "allocating %i isoc audio buffers of size %i\n",
AUDIO_ISOC_BUFFER_MANY,
peasycap->audio_isoc_buffer_size);
JOM(4, ".... each occupying contiguous memory pages\n");
for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) {
pbuf = (void *)__get_free_pages(GFP_KERNEL, AUDIO_ISOC_ORDER);
if (!pbuf) {
SAM("ERROR: Could not allocate isoc audio buffer %i\n",
k);
return -ENOMEM;
}
peasycap->allocation_audio_page += BIT(AUDIO_ISOC_ORDER);
peasycap->audio_isoc_buffer[k].pgo = pbuf;
peasycap->audio_isoc_buffer[k].pto =
pbuf + peasycap->audio_isoc_buffer_size;
peasycap->audio_isoc_buffer[k].kount = k;
}
JOM(4, "allocation of isoc audio buffers done.\n");
return 0;
}
static void free_audio_buffers(struct easycap *peasycap)
{
int k, m;
JOM(4, "freeing audio isoc buffers.\n");
m = 0;
for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) {
if (peasycap->audio_isoc_buffer[k].pgo) {
free_pages((unsigned long)
(peasycap->audio_isoc_buffer[k].pgo),
AUDIO_ISOC_ORDER);
peasycap->audio_isoc_buffer[k].pgo = NULL;
peasycap->allocation_audio_page -=
BIT(AUDIO_ISOC_ORDER);
m++;
}
}
JOM(4, "easyoss_delete(): isoc audio buffers freed: %i pages\n",
m * (0x01 << AUDIO_ISOC_ORDER));
}
static int create_audio_urbs(struct easycap *peasycap)
{
struct urb *purb;
struct data_urb *pdata_urb;
int k, j;
JOM(4, "allocating %i struct urb.\n", AUDIO_ISOC_BUFFER_MANY);
JOM(4, "using %i=peasycap->audio_isoc_framesperdesc\n",
peasycap->audio_isoc_framesperdesc);
JOM(4, "using %i=peasycap->audio_isoc_maxframesize\n",
peasycap->audio_isoc_maxframesize);
JOM(4, "using %i=peasycap->audio_isoc_buffer_size\n",
peasycap->audio_isoc_buffer_size);
for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) {
purb = usb_alloc_urb(peasycap->audio_isoc_framesperdesc,
GFP_KERNEL);
if (!purb) {
SAM("ERROR: usb_alloc_urb returned NULL for buffer "
"%i\n", k);
return -ENOMEM;
}
peasycap->allocation_audio_urb += 1 ;
pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL);
if (!pdata_urb) {
usb_free_urb(purb);
SAM("ERROR: Could not allocate struct data_urb.\n");
return -ENOMEM;
}
peasycap->allocation_audio_struct +=
sizeof(struct data_urb);
pdata_urb->purb = purb;
pdata_urb->isbuf = k;
pdata_urb->length = 0;
list_add_tail(&(pdata_urb->list_head),
peasycap->purb_audio_head);
if (!k) {
JOM(4, "initializing audio urbs thus:\n");
JOM(4, " purb->interval = 1;\n");
JOM(4, " purb->dev = peasycap->pusb_device;\n");
JOM(4, " purb->pipe = usb_rcvisocpipe(peasycap->"
"pusb_device,%i);\n",
peasycap->audio_endpointnumber);
JOM(4, " purb->transfer_flags = URB_ISO_ASAP;\n");
JOM(4, " purb->transfer_buffer = "
"peasycap->audio_isoc_buffer[.].pgo;\n");
JOM(4, " purb->transfer_buffer_length = %i;\n",
peasycap->audio_isoc_buffer_size);
JOM(4, " purb->complete = easycap_alsa_complete;\n");
JOM(4, " purb->context = peasycap;\n");
JOM(4, " purb->start_frame = 0;\n");
JOM(4, " purb->number_of_packets = %i;\n",
peasycap->audio_isoc_framesperdesc);
JOM(4, " for (j = 0; j < %i; j++)\n",
peasycap->audio_isoc_framesperdesc);
JOM(4, " {\n");
JOM(4, " purb->iso_frame_desc[j].offset = j*%i;\n",
peasycap->audio_isoc_maxframesize);
JOM(4, " purb->iso_frame_desc[j].length = %i;\n",
peasycap->audio_isoc_maxframesize);
JOM(4, " }\n");
}
purb->interval = 1;
purb->dev = peasycap->pusb_device;
purb->pipe = usb_rcvisocpipe(peasycap->pusb_device,
peasycap->audio_endpointnumber);
purb->transfer_flags = URB_ISO_ASAP;
purb->transfer_buffer = peasycap->audio_isoc_buffer[k].pgo;
purb->transfer_buffer_length =
peasycap->audio_isoc_buffer_size;
purb->complete = easycap_alsa_complete;
purb->context = peasycap;
purb->start_frame = 0;
purb->number_of_packets = peasycap->audio_isoc_framesperdesc;
for (j = 0; j < peasycap->audio_isoc_framesperdesc; j++) {
purb->iso_frame_desc[j].offset =
j * peasycap->audio_isoc_maxframesize;
purb->iso_frame_desc[j].length =
peasycap->audio_isoc_maxframesize;
}
}
JOM(4, "allocation of %i struct urb done.\n", k);
return 0;
}
static void free_audio_urbs(struct easycap *peasycap)
{
struct list_head *plist_head, *plist_next;
struct data_urb *pdata_urb;
int m;
if (peasycap->purb_audio_head) {
JOM(4, "freeing audio urbs\n");
m = 0;
list_for_each(plist_head, (peasycap->purb_audio_head)) {
pdata_urb = list_entry(plist_head,
struct data_urb, list_head);
if (pdata_urb && pdata_urb->purb) {
usb_free_urb(pdata_urb->purb);
pdata_urb->purb = NULL;
peasycap->allocation_audio_urb--;
m++;
}
}
JOM(4, "%i audio urbs freed\n", m);
JOM(4, "freeing audio data_urb structures.\n");
m = 0;
list_for_each_safe(plist_head, plist_next,
peasycap->purb_audio_head) {
pdata_urb = list_entry(plist_head,
struct data_urb, list_head);
if (pdata_urb) {
peasycap->allocation_audio_struct -=
sizeof(struct data_urb);
kfree(pdata_urb);
m++;
}
}
JOM(4, "%i audio data_urb structures freed\n", m);
JOM(4, "setting peasycap->purb_audio_head=NULL\n");
peasycap->purb_audio_head = NULL;
}
}
static void config_easycap(struct easycap *peasycap,
u8 bInterfaceNumber,
u8 bInterfaceClass,
u8 bInterfaceSubClass)
{
if ((USB_CLASS_VIDEO == bInterfaceClass) ||
(USB_CLASS_VENDOR_SPEC == bInterfaceClass)) {
if (-1 == peasycap->video_interface) {
peasycap->video_interface = bInterfaceNumber;
JOM(4, "setting peasycap->video_interface=%i\n",
peasycap->video_interface);
} else {
if (peasycap->video_interface != bInterfaceNumber) {
SAM("ERROR: attempting to reset "
"peasycap->video_interface\n");
SAM("...... continuing with "
"%i=peasycap->video_interface\n",
peasycap->video_interface);
}
}
} else if ((USB_CLASS_AUDIO == bInterfaceClass) &&
(USB_SUBCLASS_AUDIOSTREAMING == bInterfaceSubClass)) {
if (-1 == peasycap->audio_interface) {
peasycap->audio_interface = bInterfaceNumber;
JOM(4, "setting peasycap->audio_interface=%i\n",
peasycap->audio_interface);
} else {
if (peasycap->audio_interface != bInterfaceNumber) {
SAM("ERROR: attempting to reset "
"peasycap->audio_interface\n");
SAM("...... continuing with "
"%i=peasycap->audio_interface\n",
peasycap->audio_interface);
}
}
}
}
/*
* This function is called from within easycap_usb_disconnect() and is
* protected by semaphores set and cleared by easycap_usb_disconnect().
* By this stage the device has already been physically unplugged,
* so peasycap->pusb_device is no longer valid.
*/
static void easycap_delete(struct kref *pkref)
{
struct easycap *peasycap;
peasycap = container_of(pkref, struct easycap, kref);
if (!peasycap) {
SAM("ERROR: peasycap is NULL: cannot perform deletions\n");
return;
}
/* Free video urbs */
free_video_urbs(peasycap);
/* Free video isoc buffers */
free_isocbuffers(peasycap);
/* Free video field buffers */
free_fieldbuffers(peasycap);
/* Free video frame buffers */
free_framebuffers(peasycap);
/* Free audio urbs */
free_audio_urbs(peasycap);
/* Free audio isoc buffers */
free_audio_buffers(peasycap);
free_easycap(peasycap);
JOT(4, "ending.\n");
}
static const struct v4l2_file_operations v4l2_fops = {
.owner = THIS_MODULE,
.open = easycap_open_noinode,
.unlocked_ioctl = easycap_unlocked_ioctl,
.poll = easycap_poll,
.mmap = easycap_mmap,
};
static int easycap_register_video(struct easycap *peasycap)
{
/*
* FIXME: This is believed to be harmless,
* but may well be unnecessary or wrong.
*/
peasycap->video_device.v4l2_dev = NULL;
strcpy(&peasycap->video_device.name[0], "easycapdc60");
peasycap->video_device.fops = &v4l2_fops;
peasycap->video_device.minor = -1;
peasycap->video_device.release = (void *)(&videodev_release);
video_set_drvdata(&(peasycap->video_device), (void *)peasycap);
if (0 != (video_register_device(&(peasycap->video_device),
VFL_TYPE_GRABBER, -1))) {
videodev_release(&(peasycap->video_device));
return -ENODEV;
}
peasycap->registered_video++;
SAM("registered with videodev: %i=minor\n",
peasycap->video_device.minor);
peasycap->minor = peasycap->video_device.minor;
return 0;
}
/*
* When the device is plugged, this function is called three times,
* one for each interface.
*/
static int easycap_usb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *usbdev;
struct usb_host_interface *alt;
struct usb_endpoint_descriptor *ep;
struct usb_interface_descriptor *interface;
struct easycap *peasycap;
int i, j, rc;
u8 bInterfaceNumber;
u8 bInterfaceClass;
u8 bInterfaceSubClass;
int okalt[8], isokalt;
int okepn[8];
int okmps[8];
int maxpacketsize;
usbdev = interface_to_usbdev(intf);
alt = usb_altnum_to_altsetting(intf, 0);
if (!alt) {
SAY("ERROR: usb_host_interface not found\n");
return -EFAULT;
}
interface = &alt->desc;
if (!interface) {
SAY("ERROR: intf_descriptor is NULL\n");
return -EFAULT;
}
/* Get properties of probed interface */
bInterfaceNumber = interface->bInterfaceNumber;
bInterfaceClass = interface->bInterfaceClass;
bInterfaceSubClass = interface->bInterfaceSubClass;
JOT(4, "intf[%i]: num_altsetting=%i\n",
bInterfaceNumber, intf->num_altsetting);
JOT(4, "intf[%i]: cur_altsetting - altsetting=%li\n",
bInterfaceNumber,
(long int)(intf->cur_altsetting - intf->altsetting));
JOT(4, "intf[%i]: bInterfaceClass=0x%02X bInterfaceSubClass=0x%02X\n",
bInterfaceNumber, bInterfaceClass, bInterfaceSubClass);
/*
* A new struct easycap is always allocated when interface 0 is probed.
* It is not possible here to free any existing struct easycap.
* This should have been done by easycap_delete() when the device was
* physically unplugged.
* The allocated struct easycap is saved for later usage when
* interfaces 1 and 2 are probed.
*/
if (0 == bInterfaceNumber) {
/*
* Alloc structure and save it in a free slot in
* easycapdc60_dongle array
*/
peasycap = alloc_easycap(bInterfaceNumber);
if (!peasycap)
return -ENOMEM;
/* Perform basic struct initialization */
init_easycap(peasycap, usbdev, intf, bInterfaceNumber);
/* Dynamically fill in the available formats */
rc = easycap_video_fillin_formats();
if (0 > rc) {
SAM("ERROR: fillin_formats() rc = %i\n", rc);
return -EFAULT;
}
JOM(4, "%i formats available\n", rc);
/* Populate easycap.inputset[] */
rc = populate_inputset(peasycap);
if (rc < 0)
return rc;
JOM(4, "finished initialization\n");
} else {
peasycap = get_easycap(usbdev, bInterfaceNumber);
if (!peasycap)
return -ENODEV;
}
config_easycap(peasycap, bInterfaceNumber,
bInterfaceClass,
bInterfaceSubClass);
/*
* Investigate all altsettings. This is done in detail
* because USB device 05e1:0408 has disparate incarnations.
*/
isokalt = 0;
for (i = 0; i < intf->num_altsetting; i++) {
alt = usb_altnum_to_altsetting(intf, i);
if (!alt) {
SAM("ERROR: alt is NULL\n");
return -EFAULT;
}
interface = &alt->desc;
if (!interface) {
SAM("ERROR: intf_descriptor is NULL\n");
return -EFAULT;
}
if (0 == interface->bNumEndpoints)
JOM(4, "intf[%i]alt[%i] has no endpoints\n",
bInterfaceNumber, i);
for (j = 0; j < interface->bNumEndpoints; j++) {
ep = &alt->endpoint[j].desc;
if (!ep) {
SAM("ERROR: ep is NULL.\n");
SAM("...... skipping\n");
continue;
}
if (!usb_endpoint_is_isoc_in(ep)) {
JOM(4, "intf[%i]alt[%i]end[%i] is a %d endpoint\n",
bInterfaceNumber,
i, j, ep->bmAttributes);
if (usb_endpoint_dir_out(ep)) {
SAM("ERROR: OUT endpoint unexpected\n");
SAM("...... continuing\n");
}
continue;
}
switch (bInterfaceClass) {
case USB_CLASS_VIDEO:
case USB_CLASS_VENDOR_SPEC: {
if (ep->wMaxPacketSize) {
if (8 > isokalt) {
okalt[isokalt] = i;
JOM(4,
"%i=okalt[%i]\n",
okalt[isokalt],
isokalt);
okepn[isokalt] =
ep->
bEndpointAddress &
0x0F;
JOM(4,
"%i=okepn[%i]\n",
okepn[isokalt],
isokalt);
okmps[isokalt] =
le16_to_cpu(ep->
wMaxPacketSize);
JOM(4,
"%i=okmps[%i]\n",
okmps[isokalt],
isokalt);
isokalt++;
}
} else {
if (-1 == peasycap->
video_altsetting_off) {
peasycap->
video_altsetting_off =
i;
JOM(4, "%i=video_"
"altsetting_off "
"<====\n",
peasycap->
video_altsetting_off);
} else {
SAM("ERROR: peasycap"
"->video_altsetting_"
"off already set\n");
SAM("...... "
"continuing with "
"%i=peasycap->video_"
"altsetting_off\n",
peasycap->
video_altsetting_off);
}
}
break;
}
case USB_CLASS_AUDIO: {
if (bInterfaceSubClass !=
USB_SUBCLASS_AUDIOSTREAMING)
break;
if (!peasycap) {
SAM("MISTAKE: "
"peasycap is NULL\n");
return -EFAULT;
}
if (ep->wMaxPacketSize) {
if (8 > isokalt) {
okalt[isokalt] = i ;
JOM(4,
"%i=okalt[%i]\n",
okalt[isokalt],
isokalt);
okepn[isokalt] =
ep->
bEndpointAddress &
0x0F;
JOM(4,
"%i=okepn[%i]\n",
okepn[isokalt],
isokalt);
okmps[isokalt] =
le16_to_cpu(ep->
wMaxPacketSize);
JOM(4,
"%i=okmps[%i]\n",
okmps[isokalt],
isokalt);
isokalt++;
}
} else {
if (-1 == peasycap->
audio_altsetting_off) {
peasycap->
audio_altsetting_off =
i;
JOM(4, "%i=audio_"
"altsetting_off "
"<====\n",
peasycap->
audio_altsetting_off);
} else {
SAM("ERROR: peasycap"
"->audio_altsetting_"
"off already set\n");
SAM("...... "
"continuing with "
"%i=peasycap->"
"audio_altsetting_"
"off\n",
peasycap->
audio_altsetting_off);
}
}
break;
}
default:
break;
}
if (0 == ep->wMaxPacketSize) {
JOM(4, "intf[%i]alt[%i]end[%i] "
"has zero packet size\n",
bInterfaceNumber, i, j);
}
}
}
/* Perform initialization of the probed interface */
JOM(4, "initialization begins for interface %i\n",
interface->bInterfaceNumber);
switch (bInterfaceNumber) {
/* 0: Video interface */
case 0: {
if (!peasycap) {
SAM("MISTAKE: peasycap is NULL\n");
return -EFAULT;
}
if (!isokalt) {
SAM("ERROR: no viable video_altsetting_on\n");
return -ENOENT;
}
peasycap->video_altsetting_on = okalt[isokalt - 1];
JOM(4, "%i=video_altsetting_on <====\n",
peasycap->video_altsetting_on);
/* Decide video streaming parameters */
peasycap->video_endpointnumber = okepn[isokalt - 1];
JOM(4, "%i=video_endpointnumber\n", peasycap->video_endpointnumber);
maxpacketsize = okmps[isokalt - 1];
peasycap->video_isoc_maxframesize =
min(maxpacketsize, USB_2_0_MAXPACKETSIZE);
if (0 >= peasycap->video_isoc_maxframesize) {
SAM("ERROR: bad video_isoc_maxframesize\n");
SAM(" possibly because port is USB 1.1\n");
return -ENOENT;
}
JOM(4, "%i=video_isoc_maxframesize\n",
peasycap->video_isoc_maxframesize);
peasycap->video_isoc_framesperdesc = VIDEO_ISOC_FRAMESPERDESC;
JOM(4, "%i=video_isoc_framesperdesc\n",
peasycap->video_isoc_framesperdesc);
if (0 >= peasycap->video_isoc_framesperdesc) {
SAM("ERROR: bad video_isoc_framesperdesc\n");
return -ENOENT;
}
peasycap->video_isoc_buffer_size =
peasycap->video_isoc_maxframesize *
peasycap->video_isoc_framesperdesc;
JOM(4, "%i=video_isoc_buffer_size\n",
peasycap->video_isoc_buffer_size);
if ((PAGE_SIZE << VIDEO_ISOC_ORDER) <
peasycap->video_isoc_buffer_size) {
SAM("MISTAKE: peasycap->video_isoc_buffer_size too big\n");
return -EFAULT;
}
if (-1 == peasycap->video_interface) {
SAM("MISTAKE: video_interface is unset\n");
return -EFAULT;
}
if (-1 == peasycap->video_altsetting_on) {
SAM("MISTAKE: video_altsetting_on is unset\n");
return -EFAULT;
}
if (-1 == peasycap->video_altsetting_off) {
SAM("MISTAKE: video_interface_off is unset\n");
return -EFAULT;
}
if (-1 == peasycap->video_endpointnumber) {
SAM("MISTAKE: video_endpointnumber is unset\n");
return -EFAULT;
}
if (-1 == peasycap->video_isoc_maxframesize) {
SAM("MISTAKE: video_isoc_maxframesize is unset\n");
return -EFAULT;
}
if (-1 == peasycap->video_isoc_buffer_size) {
SAM("MISTAKE: video_isoc_buffer_size is unset\n");
return -EFAULT;
}
/*
* Allocate memory for video buffers.
* Lists must be initialized first.
*/
INIT_LIST_HEAD(&(peasycap->urb_video_head));
peasycap->purb_video_head = &(peasycap->urb_video_head);
rc = alloc_framebuffers(peasycap);
if (rc < 0)
return rc;
rc = alloc_fieldbuffers(peasycap);
if (rc < 0)
return rc;
rc = alloc_isocbuffers(peasycap);
if (rc < 0)
return rc;
/* Allocate and initialize video urbs */
rc = create_video_urbs(peasycap);
if (rc < 0)
return rc;
/* Save pointer peasycap in this interface */
usb_set_intfdata(intf, peasycap);
/*
* It is essential to initialize the hardware before,
* rather than after, the device is registered,
* because some udev rules triggers easycap_open()
* immediately after registration, causing a clash.
*/
rc = reset(peasycap);
if (rc) {
SAM("ERROR: reset() rc = %i\n", rc);
return -EFAULT;
}
/* The video device can now be registered */
if (v4l2_device_register(&intf->dev, &peasycap->v4l2_device)) {
SAM("v4l2_device_register() failed\n");
return -ENODEV;
}
JOM(4, "registered device instance: %s\n",
peasycap->v4l2_device.name);
rc = easycap_register_video(peasycap);
if (rc < 0) {
dev_err(&intf->dev,
"Not able to register with videodev\n");
return -ENODEV;
}
break;
}
/* 1: Audio control */
case 1: {
if (!peasycap) {
SAM("MISTAKE: peasycap is NULL\n");
return -EFAULT;
}
/* Save pointer peasycap in this interface */
usb_set_intfdata(intf, peasycap);
JOM(4, "no initialization required for interface %i\n",
interface->bInterfaceNumber);
break;
}
/* 2: Audio streaming */
case 2: {
if (!peasycap) {
SAM("MISTAKE: peasycap is NULL\n");
return -EFAULT;
}
if (!isokalt) {
SAM("ERROR: no viable audio_altsetting_on\n");
return -ENOENT;
}
peasycap->audio_altsetting_on = okalt[isokalt - 1];
JOM(4, "%i=audio_altsetting_on <====\n",
peasycap->audio_altsetting_on);
peasycap->audio_endpointnumber = okepn[isokalt - 1];
JOM(4, "%i=audio_endpointnumber\n", peasycap->audio_endpointnumber);
peasycap->audio_isoc_maxframesize = okmps[isokalt - 1];
JOM(4, "%i=audio_isoc_maxframesize\n",
peasycap->audio_isoc_maxframesize);
if (0 >= peasycap->audio_isoc_maxframesize) {
SAM("ERROR: bad audio_isoc_maxframesize\n");
return -ENOENT;
}
if (9 == peasycap->audio_isoc_maxframesize) {
peasycap->ilk |= 0x02;
SAM("audio hardware is microphone\n");
peasycap->microphone = true;
peasycap->audio_pages_per_fragment =
PAGES_PER_AUDIO_FRAGMENT;
} else if (256 == peasycap->audio_isoc_maxframesize) {
peasycap->ilk &= ~0x02;
SAM("audio hardware is AC'97\n");
peasycap->microphone = false;
peasycap->audio_pages_per_fragment =
PAGES_PER_AUDIO_FRAGMENT;
} else {
SAM("hardware is unidentified:\n");
SAM("%i=audio_isoc_maxframesize\n",
peasycap->audio_isoc_maxframesize);
return -ENOENT;
}
peasycap->audio_bytes_per_fragment =
peasycap->audio_pages_per_fragment * PAGE_SIZE;
peasycap->audio_buffer_page_many = (AUDIO_FRAGMENT_MANY *
peasycap->audio_pages_per_fragment);
JOM(4, "%6i=AUDIO_FRAGMENT_MANY\n", AUDIO_FRAGMENT_MANY);
JOM(4, "%6i=audio_pages_per_fragment\n",
peasycap->audio_pages_per_fragment);
JOM(4, "%6i=audio_bytes_per_fragment\n",
peasycap->audio_bytes_per_fragment);
JOM(4, "%6i=audio_buffer_page_many\n",
peasycap->audio_buffer_page_many);
peasycap->audio_isoc_framesperdesc = AUDIO_ISOC_FRAMESPERDESC;
JOM(4, "%i=audio_isoc_framesperdesc\n",
peasycap->audio_isoc_framesperdesc);
if (0 >= peasycap->audio_isoc_framesperdesc) {
SAM("ERROR: bad audio_isoc_framesperdesc\n");
return -ENOENT;
}
peasycap->audio_isoc_buffer_size =
peasycap->audio_isoc_maxframesize *
peasycap->audio_isoc_framesperdesc;
JOM(4, "%i=audio_isoc_buffer_size\n",
peasycap->audio_isoc_buffer_size);
if (AUDIO_ISOC_BUFFER_SIZE < peasycap->audio_isoc_buffer_size) {
SAM("MISTAKE: audio_isoc_buffer_size bigger "
"than %li=AUDIO_ISOC_BUFFER_SIZE\n",
AUDIO_ISOC_BUFFER_SIZE);
return -EFAULT;
}
if (-1 == peasycap->audio_interface) {
SAM("MISTAKE: audio_interface is unset\n");
return -EFAULT;
}
if (-1 == peasycap->audio_altsetting_on) {
SAM("MISTAKE: audio_altsetting_on is unset\n");
return -EFAULT;
}
if (-1 == peasycap->audio_altsetting_off) {
SAM("MISTAKE: audio_interface_off is unset\n");
return -EFAULT;
}
if (-1 == peasycap->audio_endpointnumber) {
SAM("MISTAKE: audio_endpointnumber is unset\n");
return -EFAULT;
}
if (-1 == peasycap->audio_isoc_maxframesize) {
SAM("MISTAKE: audio_isoc_maxframesize is unset\n");
return -EFAULT;
}
if (-1 == peasycap->audio_isoc_buffer_size) {
SAM("MISTAKE: audio_isoc_buffer_size is unset\n");
return -EFAULT;
}
/*
* Allocate memory for audio buffers.
* Lists must be initialized first.
*/
INIT_LIST_HEAD(&(peasycap->urb_audio_head));
peasycap->purb_audio_head = &(peasycap->urb_audio_head);
alloc_audio_buffers(peasycap);
if (rc < 0)
return rc;
/* Allocate and initialize urbs */
rc = create_audio_urbs(peasycap);
if (rc < 0)
return rc;
/* Save pointer peasycap in this interface */
usb_set_intfdata(intf, peasycap);
/* The audio device can now be registered */
JOM(4, "initializing ALSA card\n");
rc = easycap_alsa_probe(peasycap);
if (rc) {
dev_err(&intf->dev, "easycap_alsa_probe() rc = %i\n",
rc);
return -ENODEV;
}
JOM(8, "kref_get() with %i=kref.refcount.counter\n",
peasycap->kref.refcount.counter);
kref_get(&peasycap->kref);
peasycap->registered_audio++;
break;
}
/* Interfaces other than 0,1,2 are unexpected */
default:
JOM(4, "ERROR: unexpected interface %i\n", bInterfaceNumber);
return -EINVAL;
}
SAM("ends successfully for interface %i\n", bInterfaceNumber);
return 0;
}
/*
* When this function is called the device has already been
* physically unplugged.
* Hence, peasycap->pusb_device is no longer valid.
* This function affects alsa.
*/
static void easycap_usb_disconnect(struct usb_interface *pusb_interface)
{
struct usb_host_interface *pusb_host_interface;
struct usb_interface_descriptor *pusb_interface_descriptor;
struct easycap *peasycap;
int minor, kd;
u8 bInterfaceNumber;
JOT(4, "\n");
pusb_host_interface = pusb_interface->cur_altsetting;
if (!pusb_host_interface) {
JOT(4, "ERROR: pusb_host_interface is NULL\n");
return;
}
pusb_interface_descriptor = &(pusb_host_interface->desc);
if (!pusb_interface_descriptor) {
JOT(4, "ERROR: pusb_interface_descriptor is NULL\n");
return;
}
bInterfaceNumber = pusb_interface_descriptor->bInterfaceNumber;
minor = pusb_interface->minor;
JOT(4, "intf[%i]: minor=%i\n", bInterfaceNumber, minor);
/* There is nothing to do for Interface Number 1 */
if (1 == bInterfaceNumber)
return;
peasycap = usb_get_intfdata(pusb_interface);
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return;
}
/* If the waitqueues are not cleared a deadlock is possible */
peasycap->video_eof = 1;
peasycap->audio_eof = 1;
wake_up_interruptible(&(peasycap->wq_video));
wake_up_interruptible(&(peasycap->wq_audio));
switch (bInterfaceNumber) {
case 0:
easycap_video_kill_urbs(peasycap);
break;
case 2:
easycap_audio_kill_urbs(peasycap);
break;
default:
break;
}
/*
* Deregister
* This procedure will block until easycap_poll(),
* video and audio ioctl are all unlocked.
* If this is not done an oops can occur when an easycap
* is unplugged while the urbs are running.
*/
kd = easycap_isdongle(peasycap);
switch (bInterfaceNumber) {
case 0: {
if (0 <= kd && DONGLE_MANY > kd) {
wake_up_interruptible(&peasycap->wq_video);
JOM(4, "about to lock dongle[%i].mutex_video\n", kd);
if (mutex_lock_interruptible(&easycapdc60_dongle[kd].
mutex_video)) {
SAY("ERROR: "
"cannot lock dongle[%i].mutex_video\n", kd);
return;
}
JOM(4, "locked dongle[%i].mutex_video\n", kd);
} else {
SAY("ERROR: %i=kd is bad: cannot lock dongle\n", kd);
}
if (!peasycap->v4l2_device.name[0]) {
SAM("ERROR: peasycap->v4l2_device.name is empty\n");
if (0 <= kd && DONGLE_MANY > kd)
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
return;
}
v4l2_device_disconnect(&peasycap->v4l2_device);
JOM(4, "v4l2_device_disconnect() OK\n");
v4l2_device_unregister(&peasycap->v4l2_device);
JOM(4, "v4l2_device_unregister() OK\n");
video_unregister_device(&peasycap->video_device);
JOM(4, "intf[%i]: video_unregister_device() minor=%i\n",
bInterfaceNumber, minor);
peasycap->registered_video--;
if (0 <= kd && DONGLE_MANY > kd) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
JOM(4, "unlocked dongle[%i].mutex_video\n", kd);
}
break;
}
case 2: {
if (0 <= kd && DONGLE_MANY > kd) {
wake_up_interruptible(&peasycap->wq_audio);
JOM(4, "about to lock dongle[%i].mutex_audio\n", kd);
if (mutex_lock_interruptible(&easycapdc60_dongle[kd].
mutex_audio)) {
SAY("ERROR: "
"cannot lock dongle[%i].mutex_audio\n", kd);
return;
}
JOM(4, "locked dongle[%i].mutex_audio\n", kd);
} else
SAY("ERROR: %i=kd is bad: cannot lock dongle\n", kd);
if (0 != snd_card_free(peasycap->psnd_card)) {
SAY("ERROR: snd_card_free() failed\n");
} else {
peasycap->psnd_card = NULL;
(peasycap->registered_audio)--;
}
if (0 <= kd && DONGLE_MANY > kd) {
mutex_unlock(&easycapdc60_dongle[kd].mutex_audio);
JOM(4, "unlocked dongle[%i].mutex_audio\n", kd);
}
break;
}
default:
break;
}
/*
* If no remaining references to peasycap,
* call easycap_delete.
* (Also when alsa has been in use)
*/
if (!peasycap->kref.refcount.counter) {
SAM("ERROR: peasycap->kref.refcount.counter is zero "
"so cannot call kref_put()\n");
SAM("ending unsuccessfully: may cause memory leak\n");
return;
}
if (0 <= kd && DONGLE_MANY > kd) {
JOM(4, "about to lock dongle[%i].mutex_video\n", kd);
if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) {
SAY("ERROR: cannot lock dongle[%i].mutex_video\n", kd);
SAM("ending unsuccessfully: may cause memory leak\n");
return;
}
JOM(4, "locked dongle[%i].mutex_video\n", kd);
JOM(4, "about to lock dongle[%i].mutex_audio\n", kd);
if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_audio)) {
SAY("ERROR: cannot lock dongle[%i].mutex_audio\n", kd);
mutex_unlock(&(easycapdc60_dongle[kd].mutex_video));
JOM(4, "unlocked dongle[%i].mutex_video\n", kd);
SAM("ending unsuccessfully: may cause memory leak\n");
return;
}
JOM(4, "locked dongle[%i].mutex_audio\n", kd);
}
JOM(4, "intf[%i]: %i=peasycap->kref.refcount.counter\n",
bInterfaceNumber, (int)peasycap->kref.refcount.counter);
kref_put(&peasycap->kref, easycap_delete);
JOT(4, "intf[%i]: kref_put() done.\n", bInterfaceNumber);
if (0 <= kd && DONGLE_MANY > kd) {
mutex_unlock(&(easycapdc60_dongle[kd].mutex_audio));
JOT(4, "unlocked dongle[%i].mutex_audio\n", kd);
mutex_unlock(&easycapdc60_dongle[kd].mutex_video);
JOT(4, "unlocked dongle[%i].mutex_video\n", kd);
}
JOM(4, "ends\n");
return;
}
/* Devices supported by this driver */
static struct usb_device_id easycap_usb_device_id_table[] = {
{USB_DEVICE(USB_EASYCAP_VENDOR_ID, USB_EASYCAP_PRODUCT_ID)},
{ }
};
MODULE_DEVICE_TABLE(usb, easycap_usb_device_id_table);
static struct usb_driver easycap_usb_driver = {
.name = "easycap",
.id_table = easycap_usb_device_id_table,
.probe = easycap_usb_probe,
.disconnect = easycap_usb_disconnect,
};
static int __init easycap_module_init(void)
{
int k, rc;
printk(KERN_INFO "Easycap version: "EASYCAP_DRIVER_VERSION "\n");
JOT(4, "begins. %i=debug %i=bars %i=gain\n",
easycap_debug, easycap_bars, easycap_gain);
mutex_init(&mutex_dongle);
for (k = 0; k < DONGLE_MANY; k++) {
easycapdc60_dongle[k].peasycap = NULL;
mutex_init(&easycapdc60_dongle[k].mutex_video);
mutex_init(&easycapdc60_dongle[k].mutex_audio);
}
rc = usb_register(&easycap_usb_driver);
if (rc)
printk(KERN_ERR "Easycap: usb_register failed rc=%d\n", rc);
return rc;
}
static void __exit easycap_module_exit(void)
{
usb_deregister(&easycap_usb_driver);
}
module_init(easycap_module_init);
module_exit(easycap_module_exit);
/******************************************************************************
* *
* easycap_settings.c *
* *
******************************************************************************/
/*
*
* Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org>
*
*
* This 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, or
* (at your option) any later version.
*
* The software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*****************************************************************************/
#include "easycap.h"
/*---------------------------------------------------------------------------*/
/*
* THE LEAST SIGNIFICANT BIT OF easycap_standard.mask HAS MEANING:
* 0 => 25 fps
* 1 => 30 fps
*
* THE MOST SIGNIFICANT BIT OF easycap_standard.mask HAS MEANING:
* 0 => full framerate
* 1 => 20% framerate
*/
/*---------------------------------------------------------------------------*/
const struct easycap_standard easycap_standard[] = {
{
.mask = 0x00FF & PAL_BGHIN ,
.v4l2_standard = {
.index = PAL_BGHIN,
.id = (V4L2_STD_PAL_B |
V4L2_STD_PAL_G | V4L2_STD_PAL_H |
V4L2_STD_PAL_I | V4L2_STD_PAL_N),
.name = "PAL_BGHIN",
.frameperiod = {1, 25},
.framelines = 625,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & NTSC_N_443 ,
.v4l2_standard = {
.index = NTSC_N_443,
.id = V4L2_STD_UNKNOWN,
.name = "NTSC_N_443",
.frameperiod = {1, 25},
.framelines = 480,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & PAL_Nc ,
.v4l2_standard = {
.index = PAL_Nc,
.id = V4L2_STD_PAL_Nc,
.name = "PAL_Nc",
.frameperiod = {1, 25},
.framelines = 625,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & NTSC_N ,
.v4l2_standard = {
.index = NTSC_N,
.id = V4L2_STD_UNKNOWN,
.name = "NTSC_N",
.frameperiod = {1, 25},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & SECAM ,
.v4l2_standard = {
.index = SECAM,
.id = V4L2_STD_SECAM,
.name = "SECAM",
.frameperiod = {1, 25},
.framelines = 625,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & NTSC_M ,
.v4l2_standard = {
.index = NTSC_M,
.id = V4L2_STD_NTSC_M,
.name = "NTSC_M",
.frameperiod = {1, 30},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & NTSC_M_JP ,
.v4l2_standard = {
.index = NTSC_M_JP,
.id = V4L2_STD_NTSC_M_JP,
.name = "NTSC_M_JP",
.frameperiod = {1, 30},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & PAL_60 ,
.v4l2_standard = {
.index = PAL_60,
.id = V4L2_STD_PAL_60,
.name = "PAL_60",
.frameperiod = {1, 30},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & NTSC_443 ,
.v4l2_standard = {
.index = NTSC_443,
.id = V4L2_STD_NTSC_443,
.name = "NTSC_443",
.frameperiod = {1, 30},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x00FF & PAL_M ,
.v4l2_standard = {
.index = PAL_M,
.id = V4L2_STD_PAL_M,
.name = "PAL_M",
.frameperiod = {1, 30},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & PAL_BGHIN_SLOW),
.v4l2_standard = {
.index = PAL_BGHIN_SLOW,
.id = (V4L2_STD_PAL_B | V4L2_STD_PAL_G |
V4L2_STD_PAL_H |
V4L2_STD_PAL_I | V4L2_STD_PAL_N |
(((v4l2_std_id)0x01) << 32)),
.name = "PAL_BGHIN_SLOW",
.frameperiod = {1, 5},
.framelines = 625,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & NTSC_N_443_SLOW),
.v4l2_standard = {
.index = NTSC_N_443_SLOW,
.id = (V4L2_STD_UNKNOWN | (((v4l2_std_id)0x11) << 32)),
.name = "NTSC_N_443_SLOW",
.frameperiod = {1, 5},
.framelines = 480,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & PAL_Nc_SLOW),
.v4l2_standard = {
.index = PAL_Nc_SLOW,
.id = (V4L2_STD_PAL_Nc | (((v4l2_std_id)0x01) << 32)),
.name = "PAL_Nc_SLOW",
.frameperiod = {1, 5},
.framelines = 625,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & NTSC_N_SLOW),
.v4l2_standard = {
.index = NTSC_N_SLOW,
.id = (V4L2_STD_UNKNOWN | (((v4l2_std_id)0x21) << 32)),
.name = "NTSC_N_SLOW",
.frameperiod = {1, 5},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & SECAM_SLOW),
.v4l2_standard = {
.index = SECAM_SLOW,
.id = (V4L2_STD_SECAM | (((v4l2_std_id)0x01) << 32)),
.name = "SECAM_SLOW",
.frameperiod = {1, 5},
.framelines = 625,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & NTSC_M_SLOW),
.v4l2_standard = {
.index = NTSC_M_SLOW,
.id = (V4L2_STD_NTSC_M | (((v4l2_std_id)0x01) << 32)),
.name = "NTSC_M_SLOW",
.frameperiod = {1, 6},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & NTSC_M_JP_SLOW),
.v4l2_standard = {
.index = NTSC_M_JP_SLOW,
.id = (V4L2_STD_NTSC_M_JP |
(((v4l2_std_id)0x01) << 32)),
.name = "NTSC_M_JP_SLOW",
.frameperiod = {1, 6},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & PAL_60_SLOW),
.v4l2_standard = {
.index = PAL_60_SLOW,
.id = (V4L2_STD_PAL_60 | (((v4l2_std_id)0x01) << 32)),
.name = "PAL_60_SLOW",
.frameperiod = {1, 6},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & NTSC_443_SLOW),
.v4l2_standard = {
.index = NTSC_443_SLOW,
.id = (V4L2_STD_NTSC_443 | (((v4l2_std_id)0x01) << 32)),
.name = "NTSC_443_SLOW",
.frameperiod = {1, 6},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0x8000 | (0x00FF & PAL_M_SLOW),
.v4l2_standard = {
.index = PAL_M_SLOW,
.id = (V4L2_STD_PAL_M | (((v4l2_std_id)0x01) << 32)),
.name = "PAL_M_SLOW",
.frameperiod = {1, 6},
.framelines = 525,
.reserved = {0, 0, 0, 0}
}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.mask = 0xFFFF
}
};
/*---------------------------------------------------------------------------*/
/*
* THE 16-BIT easycap_format.mask HAS MEANING:
* (least significant) BIT 0: 0 => PAL, 25 FPS; 1 => NTSC, 30 FPS
* BITS 2-4: RESERVED FOR DIFFERENTIATING STANDARDS
* BITS 5-7: NUMBER OF BYTES PER PIXEL
* BIT 8: 0 => NATIVE BYTE ORDER; 1 => SWAPPED
* BITS 9-10: RESERVED FOR OTHER BYTE PERMUTATIONS
* BIT 11: 0 => UNDECIMATED; 1 => DECIMATED
* BIT 12: 0 => OFFER FRAMES; 1 => OFFER FIELDS
* BIT 13: 0 => FULL FRAMERATE; 1 => REDUCED
* (most significant) BITS 14-15: RESERVED FOR OTHER FIELD/FRAME OPTIONS
* IT FOLLOWS THAT:
* bytesperpixel IS ((0x00E0 & easycap_format.mask) >> 5)
* byteswaporder IS true IF (0 != (0x0100 & easycap_format.mask))
*
* decimatepixel IS true IF (0 != (0x0800 & easycap_format.mask))
*
* offerfields IS true IF (0 != (0x1000 & easycap_format.mask))
*/
/*---------------------------------------------------------------------------*/
struct easycap_format easycap_format[1 + SETTINGS_MANY];
int easycap_video_fillin_formats(void)
{
const char *name1, *name2, *name3, *name4;
struct v4l2_format *fmt;
int i, j, k, m, n;
u32 width, height, pixelformat, bytesperline, sizeimage;
u16 mask1, mask2, mask3, mask4;
enum v4l2_field field;
enum v4l2_colorspace colorspace;
for (i = 0, n = 0; i < STANDARD_MANY; i++) {
mask1 = 0x0000;
switch (i) {
case PAL_BGHIN: {
mask1 = 0x1F & PAL_BGHIN;
name1 = "PAL_BGHIN";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case SECAM: {
mask1 = 0x1F & SECAM;
name1 = "SECAM";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case PAL_Nc: {
mask1 = 0x1F & PAL_Nc;
name1 = "PAL_Nc";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case PAL_60: {
mask1 = 0x1F & PAL_60;
name1 = "PAL_60";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case PAL_M: {
mask1 = 0x1F & PAL_M;
name1 = "PAL_M";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case NTSC_M: {
mask1 = 0x1F & NTSC_M;
name1 = "NTSC_M";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case NTSC_443: {
mask1 = 0x1F & NTSC_443;
name1 = "NTSC_443";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case NTSC_M_JP: {
mask1 = 0x1F & NTSC_M_JP;
name1 = "NTSC_M_JP";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case NTSC_N: {
mask1 = 0x1F & NTSC_M;
name1 = "NTSC_N";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case NTSC_N_443: {
mask1 = 0x1F & NTSC_N_443;
name1 = "NTSC_N_443";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case PAL_BGHIN_SLOW: {
mask1 = 0x001F & PAL_BGHIN_SLOW;
mask1 |= 0x0200;
name1 = "PAL_BGHIN_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case SECAM_SLOW: {
mask1 = 0x001F & SECAM_SLOW;
mask1 |= 0x0200;
name1 = "SECAM_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case PAL_Nc_SLOW: {
mask1 = 0x001F & PAL_Nc_SLOW;
mask1 |= 0x0200;
name1 = "PAL_Nc_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case PAL_60_SLOW: {
mask1 = 0x001F & PAL_60_SLOW;
mask1 |= 0x0200;
name1 = "PAL_60_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case PAL_M_SLOW: {
mask1 = 0x001F & PAL_M_SLOW;
mask1 |= 0x0200;
name1 = "PAL_M_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
break;
}
case NTSC_M_SLOW: {
mask1 = 0x001F & NTSC_M_SLOW;
mask1 |= 0x0200;
name1 = "NTSC_M_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case NTSC_443_SLOW: {
mask1 = 0x001F & NTSC_443_SLOW;
mask1 |= 0x0200;
name1 = "NTSC_443_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case NTSC_M_JP_SLOW: {
mask1 = 0x001F & NTSC_M_JP_SLOW;
mask1 |= 0x0200;
name1 = "NTSC_M_JP_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case NTSC_N_SLOW: {
mask1 = 0x001F & NTSC_N_SLOW;
mask1 |= 0x0200;
name1 = "NTSC_N_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
case NTSC_N_443_SLOW: {
mask1 = 0x001F & NTSC_N_443_SLOW;
mask1 |= 0x0200;
name1 = "NTSC_N_443_SLOW";
colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
break;
}
default:
return -1;
}
for (j = 0; j < RESOLUTION_MANY; j++) {
mask2 = 0x0000;
switch (j) {
case AT_720x576: {
if (0x1 & mask1)
continue;
name2 = "_AT_720x576";
width = 720;
height = 576;
break;
}
case AT_704x576: {
if (0x1 & mask1)
continue;
name2 = "_AT_704x576";
width = 704;
height = 576;
break;
}
case AT_640x480: {
name2 = "_AT_640x480";
width = 640;
height = 480;
break;
}
case AT_720x480: {
if (!(0x1 & mask1))
continue;
name2 = "_AT_720x480";
width = 720;
height = 480;
break;
}
case AT_360x288: {
if (0x1 & mask1)
continue;
name2 = "_AT_360x288";
width = 360;
height = 288;
mask2 = 0x0800;
break;
}
case AT_320x240: {
name2 = "_AT_320x240";
width = 320;
height = 240;
mask2 = 0x0800;
break;
}
case AT_360x240: {
if (!(0x1 & mask1))
continue;
name2 = "_AT_360x240";
width = 360;
height = 240;
mask2 = 0x0800;
break;
}
default:
return -2;
}
for (k = 0; k < PIXELFORMAT_MANY; k++) {
mask3 = 0x0000;
switch (k) {
case FMT_UYVY: {
name3 = __stringify(FMT_UYVY);
pixelformat = V4L2_PIX_FMT_UYVY;
mask3 |= (0x02 << 5);
break;
}
case FMT_YUY2: {
name3 = __stringify(FMT_YUY2);
pixelformat = V4L2_PIX_FMT_YUYV;
mask3 |= (0x02 << 5);
mask3 |= 0x0100;
break;
}
case FMT_RGB24: {
name3 = __stringify(FMT_RGB24);
pixelformat = V4L2_PIX_FMT_RGB24;
mask3 |= (0x03 << 5);
break;
}
case FMT_RGB32: {
name3 = __stringify(FMT_RGB32);
pixelformat = V4L2_PIX_FMT_RGB32;
mask3 |= (0x04 << 5);
break;
}
case FMT_BGR24: {
name3 = __stringify(FMT_BGR24);
pixelformat = V4L2_PIX_FMT_BGR24;
mask3 |= (0x03 << 5);
mask3 |= 0x0100;
break;
}
case FMT_BGR32: {
name3 = __stringify(FMT_BGR32);
pixelformat = V4L2_PIX_FMT_BGR32;
mask3 |= (0x04 << 5);
mask3 |= 0x0100;
break;
}
default:
return -3;
}
bytesperline = width * ((mask3 & 0x00E0) >> 5);
sizeimage = bytesperline * height;
for (m = 0; m < INTERLACE_MANY; m++) {
mask4 = 0x0000;
switch (m) {
case FIELD_NONE: {
name4 = "-n";
field = V4L2_FIELD_NONE;
break;
}
case FIELD_INTERLACED: {
name4 = "-i";
mask4 |= 0x1000;
field = V4L2_FIELD_INTERLACED;
break;
}
default:
return -4;
}
if (SETTINGS_MANY <= n)
return -5;
strcpy(easycap_format[n].name, name1);
strcat(easycap_format[n].name, name2);
strcat(easycap_format[n].name, "_");
strcat(easycap_format[n].name, name3);
strcat(easycap_format[n].name, name4);
easycap_format[n].mask =
mask1 | mask2 | mask3 | mask4;
fmt = &easycap_format[n].v4l2_format;
fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt->fmt.pix.width = width;
fmt->fmt.pix.height = height;
fmt->fmt.pix.pixelformat = pixelformat;
fmt->fmt.pix.field = field;
fmt->fmt.pix.bytesperline = bytesperline;
fmt->fmt.pix.sizeimage = sizeimage;
fmt->fmt.pix.colorspace = colorspace;
fmt->fmt.pix.priv = 0;
n++;
}
}
}
}
if ((1 + SETTINGS_MANY) <= n)
return -6;
easycap_format[n].mask = 0xFFFF;
return n;
}
/*---------------------------------------------------------------------------*/
struct v4l2_queryctrl easycap_control[] = {
{
.id = V4L2_CID_BRIGHTNESS,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Brightness",
.minimum = 0,
.maximum = 255,
.step = 1,
.default_value = SAA_0A_DEFAULT,
.flags = 0,
.reserved = {0, 0}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.id = V4L2_CID_CONTRAST,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Contrast",
.minimum = 0,
.maximum = 255,
.step = 1,
.default_value = SAA_0B_DEFAULT + 128,
.flags = 0,
.reserved = {0, 0}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.id = V4L2_CID_SATURATION,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Saturation",
.minimum = 0,
.maximum = 255,
.step = 1,
.default_value = SAA_0C_DEFAULT + 128,
.flags = 0,
.reserved = {0, 0}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.id = V4L2_CID_HUE,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Hue",
.minimum = 0,
.maximum = 255,
.step = 1,
.default_value = SAA_0D_DEFAULT + 128,
.flags = 0,
.reserved = {0, 0}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.id = V4L2_CID_AUDIO_VOLUME,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Volume",
.minimum = 0,
.maximum = 31,
.step = 1,
.default_value = 16,
.flags = 0,
.reserved = {0, 0}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.id = V4L2_CID_AUDIO_MUTE,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Mute",
.default_value = true,
.flags = 0,
.reserved = {0, 0}
},
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
.id = 0xFFFFFFFF
}
};
/*****************************************************************************/
/******************************************************************************
* *
* easycap_sound.c *
* *
* Audio driver for EasyCAP USB2.0 Video Capture Device DC60 *
* *
* *
******************************************************************************/
/*
*
* Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org>
*
*
* This 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, or
* (at your option) any later version.
*
* The software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*****************************************************************************/
#include "easycap.h"
/*--------------------------------------------------------------------------*/
/*
* PARAMETERS USED WHEN REGISTERING THE AUDIO INTERFACE
*/
/*--------------------------------------------------------------------------*/
static const struct snd_pcm_hardware alsa_hardware = {
.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000,
.rate_min = 32000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = PAGE_SIZE *
PAGES_PER_AUDIO_FRAGMENT *
AUDIO_FRAGMENT_MANY,
.period_bytes_min = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT,
.period_bytes_max = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT * 2,
.periods_min = AUDIO_FRAGMENT_MANY,
.periods_max = AUDIO_FRAGMENT_MANY * 2,
};
/*---------------------------------------------------------------------------*/
/*
* SUBMIT ALL AUDIO URBS.
*/
/*---------------------------------------------------------------------------*/
static int easycap_audio_submit_urbs(struct easycap *peasycap)
{
struct data_urb *pdata_urb;
struct urb *purb;
struct list_head *plist_head;
int j, isbad, nospc, m, rc;
int isbuf;
if (!peasycap->purb_audio_head) {
SAM("ERROR: peasycap->urb_audio_head uninitialized\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -EFAULT;
}
if (peasycap->audio_isoc_streaming) {
JOM(4, "already streaming audio urbs\n");
return 0;
}
JOM(4, "initial submission of all audio urbs\n");
rc = usb_set_interface(peasycap->pusb_device,
peasycap->audio_interface,
peasycap->audio_altsetting_on);
JOM(8, "usb_set_interface(.,%i,%i) returned %i\n",
peasycap->audio_interface,
peasycap->audio_altsetting_on, rc);
isbad = 0;
nospc = 0;
m = 0;
list_for_each(plist_head, peasycap->purb_audio_head) {
pdata_urb = list_entry(plist_head, struct data_urb, list_head);
if (pdata_urb && pdata_urb->purb) {
purb = pdata_urb->purb;
isbuf = pdata_urb->isbuf;
purb->interval = 1;
purb->dev = peasycap->pusb_device;
purb->pipe = usb_rcvisocpipe(peasycap->pusb_device,
peasycap->audio_endpointnumber);
purb->transfer_flags = URB_ISO_ASAP;
purb->transfer_buffer = peasycap->audio_isoc_buffer[isbuf].pgo;
purb->transfer_buffer_length = peasycap->audio_isoc_buffer_size;
purb->complete = easycap_alsa_complete;
purb->context = peasycap;
purb->start_frame = 0;
purb->number_of_packets = peasycap->audio_isoc_framesperdesc;
for (j = 0; j < peasycap->audio_isoc_framesperdesc; j++) {
purb->iso_frame_desc[j].offset = j * peasycap->audio_isoc_maxframesize;
purb->iso_frame_desc[j].length = peasycap->audio_isoc_maxframesize;
}
rc = usb_submit_urb(purb, GFP_KERNEL);
if (rc) {
isbad++;
SAM("ERROR: usb_submit_urb() failed"
" for urb with rc: -%s: %d\n",
strerror(rc), rc);
} else {
m++;
}
} else {
isbad++;
}
}
if (nospc) {
SAM("-ENOSPC=usb_submit_urb() for %i urbs\n", nospc);
SAM("..... possibly inadequate USB bandwidth\n");
peasycap->audio_eof = 1;
}
if (isbad)
easycap_audio_kill_urbs(peasycap);
else
peasycap->audio_isoc_streaming = m;
return 0;
}
/*---------------------------------------------------------------------------*/
/*
* COMMON AUDIO INITIALIZATION
*/
/*---------------------------------------------------------------------------*/
static int easycap_sound_setup(struct easycap *peasycap)
{
int rc;
JOM(4, "starting initialization\n");
if (!peasycap) {
SAY("ERROR: peasycap is NULL.\n");
return -EFAULT;
}
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device is NULL\n");
return -ENODEV;
}
JOM(16, "0x%08lX=peasycap->pusb_device\n", (long int)peasycap->pusb_device);
rc = easycap_audio_setup(peasycap);
JOM(8, "audio_setup() returned %i\n", rc);
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device has become NULL\n");
return -ENODEV;
}
/*---------------------------------------------------------------------------*/
if (!peasycap->pusb_device) {
SAM("ERROR: peasycap->pusb_device has become NULL\n");
return -ENODEV;
}
rc = usb_set_interface(peasycap->pusb_device, peasycap->audio_interface,
peasycap->audio_altsetting_on);
JOM(8, "usb_set_interface(.,%i,%i) returned %i\n", peasycap->audio_interface,
peasycap->audio_altsetting_on, rc);
rc = easycap_wakeup_device(peasycap->pusb_device);
JOM(8, "wakeup_device() returned %i\n", rc);
peasycap->audio_eof = 0;
peasycap->audio_idle = 0;
easycap_audio_submit_urbs(peasycap);
JOM(4, "finished initialization\n");
return 0;
}
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* ON COMPLETION OF AN AUDIO URB ITS DATA IS COPIED TO THE DAM BUFFER
* PROVIDED peasycap->audio_idle IS ZERO. REGARDLESS OF THIS BEING TRUE,
* IT IS RESUBMITTED PROVIDED peasycap->audio_isoc_streaming IS NOT ZERO.
*/
/*---------------------------------------------------------------------------*/
void easycap_alsa_complete(struct urb *purb)
{
struct easycap *peasycap;
struct snd_pcm_substream *pss;
struct snd_pcm_runtime *prt;
int dma_bytes, fragment_bytes;
int isfragment;
u8 *p1, *p2;
s16 tmp;
int i, j, more, much, rc;
#ifdef UPSAMPLE
int k;
s16 oldaudio, newaudio, delta;
#endif /*UPSAMPLE*/
JOT(16, "\n");
if (!purb) {
SAY("ERROR: purb is NULL\n");
return;
}
peasycap = purb->context;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return;
}
much = 0;
if (peasycap->audio_idle) {
JOM(16, "%i=audio_idle %i=audio_isoc_streaming\n",
peasycap->audio_idle, peasycap->audio_isoc_streaming);
if (peasycap->audio_isoc_streaming)
goto resubmit;
}
/*---------------------------------------------------------------------------*/
pss = peasycap->psubstream;
if (!pss)
goto resubmit;
prt = pss->runtime;
if (!prt)
goto resubmit;
dma_bytes = (int)prt->dma_bytes;
if (0 == dma_bytes)
goto resubmit;
fragment_bytes = 4 * ((int)prt->period_size);
if (0 == fragment_bytes)
goto resubmit;
/* -------------------------------------------------------------------------*/
if (purb->status) {
if ((-ESHUTDOWN == purb->status) || (-ENOENT == purb->status)) {
JOM(16, "urb status -ESHUTDOWN or -ENOENT\n");
return;
}
SAM("ERROR: non-zero urb status: -%s: %d\n",
strerror(purb->status), purb->status);
goto resubmit;
}
/*---------------------------------------------------------------------------*/
/*
* PROCEED HERE WHEN NO ERROR
*/
/*---------------------------------------------------------------------------*/
#ifdef UPSAMPLE
oldaudio = peasycap->oldaudio;
#endif /*UPSAMPLE*/
for (i = 0; i < purb->number_of_packets; i++) {
if (purb->iso_frame_desc[i].status < 0) {
SAM("-%s: %d\n",
strerror(purb->iso_frame_desc[i].status),
purb->iso_frame_desc[i].status);
}
if (purb->iso_frame_desc[i].status) {
JOM(12, "discarding audio samples because "
"%i=purb->iso_frame_desc[i].status\n",
purb->iso_frame_desc[i].status);
continue;
}
more = purb->iso_frame_desc[i].actual_length;
if (more == 0) {
peasycap->audio_mt++;
continue;
}
if (0 > more) {
SAM("MISTAKE: more is negative\n");
return;
}
if (peasycap->audio_mt) {
JOM(12, "%4i empty audio urb frames\n",
peasycap->audio_mt);
peasycap->audio_mt = 0;
}
p1 = (u8 *)(purb->transfer_buffer +
purb->iso_frame_desc[i].offset);
/*
* COPY more BYTES FROM ISOC BUFFER
* TO THE DMA BUFFER, CONVERTING
* 8-BIT MONO TO 16-BIT SIGNED
* LITTLE-ENDIAN SAMPLES IF NECESSARY
*/
while (more) {
much = dma_bytes - peasycap->dma_fill;
if (0 > much) {
SAM("MISTAKE: much is negative\n");
return;
}
if (0 == much) {
peasycap->dma_fill = 0;
peasycap->dma_next = fragment_bytes;
JOM(8, "wrapped dma buffer\n");
}
if (!peasycap->microphone) {
if (much > more)
much = more;
memcpy(prt->dma_area + peasycap->dma_fill,
p1, much);
p1 += much;
more -= much;
} else {
#ifdef UPSAMPLE
if (much % 16)
JOM(8, "MISTAKE? much"
" is not divisible by 16\n");
if (much > (16 * more))
much = 16 * more;
p2 = (u8 *)(prt->dma_area + peasycap->dma_fill);
for (j = 0; j < (much / 16); j++) {
newaudio = ((int) *p1) - 128;
newaudio = 128 * newaudio;
delta = (newaudio - oldaudio) / 4;
tmp = oldaudio + delta;
for (k = 0; k < 4; k++) {
*p2 = (0x00FF & tmp);
*(p2 + 1) = (0xFF00 & tmp) >> 8;
p2 += 2;
*p2 = (0x00FF & tmp);
*(p2 + 1) = (0xFF00 & tmp) >> 8;
p2 += 2;
tmp += delta;
}
p1++;
more--;
oldaudio = tmp;
}
#else /*!UPSAMPLE*/
if (much > (2 * more))
much = 2 * more;
p2 = (u8 *)(prt->dma_area + peasycap->dma_fill);
for (j = 0; j < (much / 2); j++) {
tmp = ((int) *p1) - 128;
tmp = 128 * tmp;
*p2 = (0x00FF & tmp);
*(p2 + 1) = (0xFF00 & tmp) >> 8;
p1++;
p2 += 2;
more--;
}
#endif /*UPSAMPLE*/
}
peasycap->dma_fill += much;
if (peasycap->dma_fill >= peasycap->dma_next) {
isfragment = peasycap->dma_fill / fragment_bytes;
if (0 > isfragment) {
SAM("MISTAKE: isfragment is negative\n");
return;
}
peasycap->dma_read = (isfragment - 1) * fragment_bytes;
peasycap->dma_next = (isfragment + 1) * fragment_bytes;
if (dma_bytes < peasycap->dma_next)
peasycap->dma_next = fragment_bytes;
if (0 <= peasycap->dma_read) {
JOM(8, "snd_pcm_period_elapsed(), %i="
"isfragment\n", isfragment);
snd_pcm_period_elapsed(pss);
}
}
}
#ifdef UPSAMPLE
peasycap->oldaudio = oldaudio;
#endif /*UPSAMPLE*/
}
/*---------------------------------------------------------------------------*/
/*
* RESUBMIT THIS URB
*/
/*---------------------------------------------------------------------------*/
resubmit:
if (peasycap->audio_isoc_streaming == 0)
return;
rc = usb_submit_urb(purb, GFP_ATOMIC);
if (rc) {
if ((-ENODEV != rc) && (-ENOENT != rc)) {
SAM("ERROR: while %i=audio_idle, usb_submit_urb failed "
"with rc: -%s :%d\n",
peasycap->audio_idle, strerror(rc), rc);
}
if (0 < peasycap->audio_isoc_streaming)
peasycap->audio_isoc_streaming--;
}
return;
}
/*****************************************************************************/
static int easycap_alsa_open(struct snd_pcm_substream *pss)
{
struct snd_pcm *psnd_pcm;
struct snd_card *psnd_card;
struct easycap *peasycap;
JOT(4, "\n");
if (!pss) {
SAY("ERROR: pss is NULL\n");
return -EFAULT;
}
psnd_pcm = pss->pcm;
if (!psnd_pcm) {
SAY("ERROR: psnd_pcm is NULL\n");
return -EFAULT;
}
psnd_card = psnd_pcm->card;
if (!psnd_card) {
SAY("ERROR: psnd_card is NULL\n");
return -EFAULT;
}
peasycap = psnd_card->private_data;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if (peasycap->psnd_card != psnd_card) {
SAM("ERROR: bad peasycap->psnd_card\n");
return -EFAULT;
}
if (peasycap->psubstream) {
SAM("ERROR: bad peasycap->psubstream\n");
return -EFAULT;
}
pss->private_data = peasycap;
peasycap->psubstream = pss;
pss->runtime->hw = peasycap->alsa_hardware;
pss->runtime->private_data = peasycap;
pss->private_data = peasycap;
if (0 != easycap_sound_setup(peasycap)) {
JOM(4, "ending unsuccessfully\n");
return -EFAULT;
}
JOM(4, "ending successfully\n");
return 0;
}
/*****************************************************************************/
static int easycap_alsa_close(struct snd_pcm_substream *pss)
{
struct easycap *peasycap;
JOT(4, "\n");
if (!pss) {
SAY("ERROR: pss is NULL\n");
return -EFAULT;
}
peasycap = snd_pcm_substream_chip(pss);
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
pss->private_data = NULL;
peasycap->psubstream = NULL;
JOT(4, "ending successfully\n");
return 0;
}
/*****************************************************************************/
static int easycap_alsa_vmalloc(struct snd_pcm_substream *pss, size_t sz)
{
struct snd_pcm_runtime *prt;
JOT(4, "\n");
if (!pss) {
SAY("ERROR: pss is NULL\n");
return -EFAULT;
}
prt = pss->runtime;
if (!prt) {
SAY("ERROR: substream.runtime is NULL\n");
return -EFAULT;
}
if (prt->dma_area) {
if (prt->dma_bytes > sz)
return 0;
vfree(prt->dma_area);
}
prt->dma_area = vmalloc(sz);
if (!prt->dma_area)
return -ENOMEM;
prt->dma_bytes = sz;
return 0;
}
/*****************************************************************************/
static int easycap_alsa_hw_params(struct snd_pcm_substream *pss,
struct snd_pcm_hw_params *phw)
{
int rc;
JOT(4, "%i\n", (params_buffer_bytes(phw)));
if (!pss) {
SAY("ERROR: pss is NULL\n");
return -EFAULT;
}
rc = easycap_alsa_vmalloc(pss, params_buffer_bytes(phw));
if (rc)
return rc;
return 0;
}
/*****************************************************************************/
static int easycap_alsa_hw_free(struct snd_pcm_substream *pss)
{
struct snd_pcm_runtime *prt;
JOT(4, "\n");
if (!pss) {
SAY("ERROR: pss is NULL\n");
return -EFAULT;
}
prt = pss->runtime;
if (!prt) {
SAY("ERROR: substream.runtime is NULL\n");
return -EFAULT;
}
if (prt->dma_area) {
JOT(8, "prt->dma_area = %p\n", prt->dma_area);
vfree(prt->dma_area);
prt->dma_area = NULL;
} else
JOT(8, "dma_area already freed\n");
return 0;
}
/*****************************************************************************/
static int easycap_alsa_prepare(struct snd_pcm_substream *pss)
{
struct easycap *peasycap;
struct snd_pcm_runtime *prt;
JOT(4, "\n");
if (!pss) {
SAY("ERROR: pss is NULL\n");
return -EFAULT;
}
prt = pss->runtime;
peasycap = snd_pcm_substream_chip(pss);
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
JOM(16, "ALSA decides %8i Hz=rate\n", pss->runtime->rate);
JOM(16, "ALSA decides %8ld =period_size\n", pss->runtime->period_size);
JOM(16, "ALSA decides %8i =periods\n", pss->runtime->periods);
JOM(16, "ALSA decides %8ld =buffer_size\n", pss->runtime->buffer_size);
JOM(16, "ALSA decides %8zd =dma_bytes\n", pss->runtime->dma_bytes);
JOM(16, "ALSA decides %8ld =boundary\n", pss->runtime->boundary);
JOM(16, "ALSA decides %8i =period_step\n", pss->runtime->period_step);
JOM(16, "ALSA decides %8i =sample_bits\n", pss->runtime->sample_bits);
JOM(16, "ALSA decides %8i =frame_bits\n", pss->runtime->frame_bits);
JOM(16, "ALSA decides %8ld =min_align\n", pss->runtime->min_align);
JOM(12, "ALSA decides %8ld =hw_ptr_base\n", pss->runtime->hw_ptr_base);
JOM(12, "ALSA decides %8ld =hw_ptr_interrupt\n",
pss->runtime->hw_ptr_interrupt);
if (prt->dma_bytes != 4 * ((int)prt->period_size) * ((int)prt->periods)) {
SAY("MISTAKE: unexpected ALSA parameters\n");
return -ENOENT;
}
return 0;
}
/*****************************************************************************/
static int easycap_alsa_ack(struct snd_pcm_substream *pss)
{
return 0;
}
/*****************************************************************************/
static int easycap_alsa_trigger(struct snd_pcm_substream *pss, int cmd)
{
struct easycap *peasycap;
JOT(4, "%i=cmd cf %i=START %i=STOP\n", cmd, SNDRV_PCM_TRIGGER_START,
SNDRV_PCM_TRIGGER_STOP);
if (!pss) {
SAY("ERROR: pss is NULL\n");
return -EFAULT;
}
peasycap = snd_pcm_substream_chip(pss);
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_START: {
peasycap->audio_idle = 0;
break;
}
case SNDRV_PCM_TRIGGER_STOP: {
peasycap->audio_idle = 1;
break;
}
default:
return -EINVAL;
}
return 0;
}
/*****************************************************************************/
static snd_pcm_uframes_t easycap_alsa_pointer(struct snd_pcm_substream *pss)
{
struct easycap *peasycap;
snd_pcm_uframes_t offset;
JOT(16, "\n");
if (!pss) {
SAY("ERROR: pss is NULL\n");
return -EFAULT;
}
peasycap = snd_pcm_substream_chip(pss);
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -EFAULT;
}
if ((0 != peasycap->audio_eof) || (0 != peasycap->audio_idle)) {
JOM(8, "returning -EIO because "
"%i=audio_idle %i=audio_eof\n",
peasycap->audio_idle, peasycap->audio_eof);
return -EIO;
}
/*---------------------------------------------------------------------------*/
if (0 > peasycap->dma_read) {
JOM(8, "returning -EBUSY\n");
return -EBUSY;
}
offset = ((snd_pcm_uframes_t)peasycap->dma_read)/4;
JOM(8, "ALSA decides %8i =hw_ptr_base\n", (int)pss->runtime->hw_ptr_base);
JOM(8, "ALSA decides %8i =hw_ptr_interrupt\n",
(int)pss->runtime->hw_ptr_interrupt);
JOM(8, "%7i=offset %7i=dma_read %7i=dma_next\n",
(int)offset, peasycap->dma_read, peasycap->dma_next);
return offset;
}
/*****************************************************************************/
static struct page *
easycap_alsa_page(struct snd_pcm_substream *pss, unsigned long offset)
{
return vmalloc_to_page(pss->runtime->dma_area + offset);
}
/*****************************************************************************/
static struct snd_pcm_ops easycap_alsa_pcm_ops = {
.open = easycap_alsa_open,
.close = easycap_alsa_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = easycap_alsa_hw_params,
.hw_free = easycap_alsa_hw_free,
.prepare = easycap_alsa_prepare,
.ack = easycap_alsa_ack,
.trigger = easycap_alsa_trigger,
.pointer = easycap_alsa_pointer,
.page = easycap_alsa_page,
};
/*****************************************************************************/
/*---------------------------------------------------------------------------*/
/*
* THE FUNCTION snd_card_create() HAS THIS_MODULE AS AN ARGUMENT. THIS
* MEANS MODULE easycap. BEWARE.
*/
/*---------------------------------------------------------------------------*/
int easycap_alsa_probe(struct easycap *peasycap)
{
int rc;
struct snd_card *psnd_card;
struct snd_pcm *psnd_pcm;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return -ENODEV;
}
if (0 > peasycap->minor) {
SAY("ERROR: no minor\n");
return -ENODEV;
}
peasycap->alsa_hardware = alsa_hardware;
if (peasycap->microphone) {
peasycap->alsa_hardware.rates = SNDRV_PCM_RATE_32000;
peasycap->alsa_hardware.rate_min = 32000;
peasycap->alsa_hardware.rate_max = 32000;
} else {
peasycap->alsa_hardware.rates = SNDRV_PCM_RATE_48000;
peasycap->alsa_hardware.rate_min = 48000;
peasycap->alsa_hardware.rate_max = 48000;
}
if (0 != snd_card_create(SNDRV_DEFAULT_IDX1, "easycap_alsa",
THIS_MODULE, 0, &psnd_card)) {
SAY("ERROR: Cannot do ALSA snd_card_create()\n");
return -EFAULT;
}
sprintf(&psnd_card->id[0], "EasyALSA%i", peasycap->minor);
strcpy(&psnd_card->driver[0], EASYCAP_DRIVER_DESCRIPTION);
strcpy(&psnd_card->shortname[0], "easycap_alsa");
sprintf(&psnd_card->longname[0], "%s", &psnd_card->shortname[0]);
psnd_card->dev = &peasycap->pusb_device->dev;
psnd_card->private_data = peasycap;
peasycap->psnd_card = psnd_card;
rc = snd_pcm_new(psnd_card, "easycap_pcm", 0, 0, 1, &psnd_pcm);
if (rc) {
SAM("ERROR: Cannot do ALSA snd_pcm_new()\n");
snd_card_free(psnd_card);
return -EFAULT;
}
snd_pcm_set_ops(psnd_pcm, SNDRV_PCM_STREAM_CAPTURE,
&easycap_alsa_pcm_ops);
psnd_pcm->info_flags = 0;
strcpy(&psnd_pcm->name[0], &psnd_card->id[0]);
psnd_pcm->private_data = peasycap;
peasycap->psnd_pcm = psnd_pcm;
peasycap->psubstream = NULL;
rc = snd_card_register(psnd_card);
if (rc) {
SAM("ERROR: Cannot do ALSA snd_card_register()\n");
snd_card_free(psnd_card);
return -EFAULT;
}
SAM("registered %s\n", &psnd_card->id[0]);
return 0;
}
/******************************************************************************
* *
* easycap_testcard.c *
* *
******************************************************************************/
/*
*
* Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org>
*
*
* This 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, or
* (at your option) any later version.
*
* The software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*****************************************************************************/
#include "easycap.h"
/*****************************************************************************/
#define TESTCARD_BYTESPERLINE (2 * 720)
void
easycap_testcard(struct easycap *peasycap, int field)
{
int total;
int y, u, v, r, g, b;
unsigned char uyvy[4];
int i1, line, k, m, n, more, much, barwidth, barheight;
unsigned char bfbar[TESTCARD_BYTESPERLINE / 8], *p1, *p2;
struct data_buffer *pfield_buffer;
if (!peasycap) {
SAY("ERROR: peasycap is NULL\n");
return;
}
JOM(8, "%i=field\n", field);
switch (peasycap->width) {
case 720:
case 360: {
barwidth = (2 * 720) / 8;
break;
}
case 704:
case 352: {
barwidth = (2 * 704) / 8;
break;
}
case 640:
case 320: {
barwidth = (2 * 640) / 8;
break;
}
default: {
SAM("ERROR: cannot set barwidth\n");
return;
}
}
if (TESTCARD_BYTESPERLINE < barwidth) {
SAM("ERROR: barwidth is too large\n");
return;
}
switch (peasycap->height) {
case 576:
case 288: {
barheight = 576;
break;
}
case 480:
case 240: {
barheight = 480;
break;
}
default: {
SAM("ERROR: cannot set barheight\n");
return;
}
}
total = 0;
k = field;
m = 0;
n = 0;
for (line = 0; line < (barheight / 2); line++) {
for (i1 = 0; i1 < 8; i1++) {
r = (i1 * 256)/8;
g = (i1 * 256)/8;
b = (i1 * 256)/8;
y = 299*r/1000 + 587*g/1000 + 114*b/1000 ;
u = -147*r/1000 - 289*g/1000 + 436*b/1000 ;
u = u + 128;
v = 615*r/1000 - 515*g/1000 - 100*b/1000 ;
v = v + 128;
uyvy[0] = 0xFF & u ;
uyvy[1] = 0xFF & y ;
uyvy[2] = 0xFF & v ;
uyvy[3] = 0xFF & y ;
p1 = &bfbar[0];
while (p1 < &bfbar[barwidth]) {
*p1++ = uyvy[0] ;
*p1++ = uyvy[1] ;
*p1++ = uyvy[2] ;
*p1++ = uyvy[3] ;
total += 4;
}
p1 = &bfbar[0];
more = barwidth;
while (more) {
if ((FIELD_BUFFER_SIZE/PAGE_SIZE) <= m) {
SAM("ERROR: bad m reached\n");
return;
}
if (PAGE_SIZE < n) {
SAM("ERROR: bad n reached\n");
return;
}
if (0 > more) {
SAM("ERROR: internal fault\n");
return;
}
much = PAGE_SIZE - n;
if (much > more)
much = more;
pfield_buffer = &peasycap->field_buffer[k][m];
p2 = pfield_buffer->pgo + n;
memcpy(p2, p1, much);
p1 += much;
n += much;
more -= much;
if (PAGE_SIZE == n) {
m++;
n = 0;
}
}
}
}
return;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册