diff options
author | Mauro Carvalho Chehab | 2011-11-02 01:23:55 +0100 |
---|---|---|
committer | Mauro Carvalho Chehab | 2011-11-03 10:59:03 +0100 |
commit | 4860c73804c6e7ef8e69f98958489bb2bea6f6d2 (patch) | |
tree | 6d8dc4ca7794478274e011e5fae4fd1005cf6877 /drivers/staging/media/easycap | |
parent | [media] move cx25821 out of staging (diff) | |
download | kernel-qcow2-linux-4860c73804c6e7ef8e69f98958489bb2bea6f6d2.tar.gz kernel-qcow2-linux-4860c73804c6e7ef8e69f98958489bb2bea6f6d2.tar.xz kernel-qcow2-linux-4860c73804c6e7ef8e69f98958489bb2bea6f6d2.zip |
staging: Move media drivers to staging/media
In practice, it is being hard to distinguish when a patch
should go to staging tree or to the media tree. Better
to distinguish it, by putting the media drivers at a
separate staging directory. Newer staging drivers that include
anything with "dvb*.h", "v4l2*.h" or "videodev2.h" should
go to the drivers/staging/media tree.
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/staging/media/easycap')
-rw-r--r-- | drivers/staging/media/easycap/Kconfig | 30 | ||||
-rw-r--r-- | drivers/staging/media/easycap/Makefile | 10 | ||||
-rw-r--r-- | drivers/staging/media/easycap/README | 141 | ||||
-rw-r--r-- | drivers/staging/media/easycap/easycap.h | 594 | ||||
-rw-r--r-- | drivers/staging/media/easycap/easycap_ioctl.c | 2450 | ||||
-rw-r--r-- | drivers/staging/media/easycap/easycap_low.c | 1129 | ||||
-rw-r--r-- | drivers/staging/media/easycap/easycap_main.c | 4253 | ||||
-rw-r--r-- | drivers/staging/media/easycap/easycap_settings.c | 696 | ||||
-rw-r--r-- | drivers/staging/media/easycap/easycap_sound.c | 816 | ||||
-rw-r--r-- | drivers/staging/media/easycap/easycap_testcard.c | 155 |
10 files changed, 10274 insertions, 0 deletions
diff --git a/drivers/staging/media/easycap/Kconfig b/drivers/staging/media/easycap/Kconfig new file mode 100644 index 000000000000..a425a6f9cdca --- /dev/null +++ b/drivers/staging/media/easycap/Kconfig @@ -0,0 +1,30 @@ +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] + diff --git a/drivers/staging/media/easycap/Makefile b/drivers/staging/media/easycap/Makefile new file mode 100644 index 000000000000..a34e75f59c18 --- /dev/null +++ b/drivers/staging/media/easycap/Makefile @@ -0,0 +1,10 @@ +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 + diff --git a/drivers/staging/media/easycap/README b/drivers/staging/media/easycap/README new file mode 100644 index 000000000000..796b032384bd --- /dev/null +++ b/drivers/staging/media/easycap/README @@ -0,0 +1,141 @@ + + *********************************************************** + * 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. + diff --git a/drivers/staging/media/easycap/easycap.h b/drivers/staging/media/easycap/easycap.h new file mode 100644 index 000000000000..7b256a948c27 --- /dev/null +++ b/drivers/staging/media/easycap/easycap.h @@ -0,0 +1,594 @@ +/***************************************************************************** +* * +* 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 USB_SKEL_MINOR_BASE 192 +#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]; + + long long int dnbydt; + + 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; + long long audio_bytes; + 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 audio_buffer_many; + + 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 + */ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +long easycap_unlocked_ioctl(struct file *, unsigned int, unsigned long); +int easycap_dqbuf(struct easycap *, int); +int submit_video_urbs(struct easycap *); +int kill_video_urbs(struct easycap *); +int field2frame(struct easycap *); +int redaub(struct easycap *, void *, void *, + int, int, u8, u8, bool); +void easycap_testcard(struct easycap *, int); +int fillin_formats(void); +int newinput(struct easycap *, int); +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); +int adjust_volume(struct easycap *, int); +/*---------------------------------------------------------------------------*/ +/* + * AUDIO FUNCTION PROTOTYPES + */ +/*---------------------------------------------------------------------------*/ +int easycap_alsa_probe(struct easycap *); +void easycap_alsa_complete(struct urb *); + +int easycap_sound_setup(struct easycap *); +int submit_audio_urbs(struct easycap *); +int kill_audio_urbs(struct easycap *); +void easyoss_testtone(struct easycap *, int); +int audio_setup(struct easycap *); +/*---------------------------------------------------------------------------*/ +/* + * LOW-LEVEL FUNCTION PROTOTYPES + */ +/*---------------------------------------------------------------------------*/ +int audio_gainget(struct usb_device *); +int audio_gainset(struct usb_device *, s8); + +int set_interface(struct usb_device *, u16); +int wakeup_device(struct usb_device *); +int confirm_resolution(struct usb_device *); +int confirm_stream(struct usb_device *); + +int setup_stk(struct usb_device *, bool); +int setup_saa(struct usb_device *, bool); +int setup_vt(struct usb_device *); +int check_stk(struct usb_device *, bool); +int check_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 read_stk(struct usb_device *, u32); +int write_saa(struct usb_device *, u16, u16); +int write_000(struct usb_device *, u16, u16); +int start_100(struct usb_device *); +int stop_100(struct usb_device *); +int write_300(struct usb_device *); +int read_vt(struct usb_device *, u16); +int write_vt(struct usb_device *, u16, u16); +int isdongle(struct easycap *); +/*---------------------------------------------------------------------------*/ + + +/*---------------------------------------------------------------------------*/ +/* + * 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 usb_driver easycap_usb_driver; +extern struct easycap_dongle easycapdc60_dongle[]; + +#endif /* !__EASYCAP_H__ */ diff --git a/drivers/staging/media/easycap/easycap_ioctl.c b/drivers/staging/media/easycap/easycap_ioctl.c new file mode 100644 index 000000000000..c99addfb6242 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_ioctl.c @@ -0,0 +1,2450 @@ +/****************************************************************************** +* * +* 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 <linux/version.h> +#include "easycap.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; + kill_video_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) + submit_video_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; + kill_video_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) + submit_video_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("adjusting brightness to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust brightness " + "to 0x%02X\n", mood); + return -ENOENT; + } + break; + } + 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("adjusting contrast to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust contrast to " + "0x%02X\n", mood); + return -ENOENT; + } + break; + } + 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("adjusting saturation to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust saturation to " + "0x%02X\n", mood); + return -ENOENT; + } + 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("adjusting hue to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust hue to 0x%02X\n", mood); + return -ENOENT; + } + break; + } + i1++; + } + SAM("WARNING: failed to adjust hue: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +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 (!audio_gainset(peasycap->pusb_device, mood)) { + SAM("adjusting volume to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust volume to " + "0x%2X\n", mood); + return -ENOENT; + } + break; + } + 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 = 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 != 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 = 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_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; + } + submit_video_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; +} +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_low.c b/drivers/staging/media/easycap/easycap_low.c new file mode 100644 index 000000000000..0385735ac6df --- /dev/null +++ b/drivers/staging/media/easycap/easycap_low.c @@ -0,0 +1,1129 @@ +/***************************************************************************** +* * +* * +* 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 { + int reg; + int set; +} stk1160configPAL[256] = { + {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[256] = { + {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 { + int reg; + int set; +} saa7113configPAL[256] = { + {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[256] = { + {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 confirm_resolution(struct usb_device *p) +{ + u8 get0, get1, get2, get3, get4, get5, get6, get7; + + if (!p) + return -ENODEV; + GET(p, 0x0110, &get0); + GET(p, 0x0111, &get1); + GET(p, 0x0112, &get2); + GET(p, 0x0113, &get3); + GET(p, 0x0114, &get4); + GET(p, 0x0115, &get5); + GET(p, 0x0116, &get6); + GET(p, 0x0117, &get7); + JOT(8, "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X\n", + get0, get1, get2, get3, get4, get5, get6, get7); + JOT(8, "....cf PAL_720x526: " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X\n", + 0x000, 0x000, 0x001, 0x000, 0x5A0, 0x005, 0x121, 0x001); + JOT(8, "....cf PAL_704x526: " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X\n", + 0x004, 0x000, 0x001, 0x000, 0x584, 0x005, 0x121, 0x001); + JOT(8, "....cf VGA_640x480: " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X\n", + 0x008, 0x000, 0x020, 0x000, 0x508, 0x005, 0x110, 0x001); + return 0; +} +/****************************************************************************/ +int confirm_stream(struct usb_device *p) +{ + u16 get2; + u8 igot; + + if (!p) + return -ENODEV; + GET(p, 0x0100, &igot); get2 = 0x80 & igot; + if (0x80 == get2) + JOT(8, "confirm_stream: OK\n"); + else + JOT(8, "confirm_stream: STUCK\n"); + 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, ir; + const struct saa7113config *cfg; + if (!p) + return -ENODEV; + cfg = (ntsc) ? saa7113configNTSC : saa7113configPAL; + for (i = 0; cfg[i].reg != 0xFF; i++) + ir = write_saa(p, cfg[i].reg, cfg[i].set); + return 0; +} +/****************************************************************************/ +int write_000(struct usb_device *p, u16 set2, u16 set0) +{ + u8 igot0, igot2; + + if (!p) + return -ENODEV; + GET(p, 0x0002, &igot2); + GET(p, 0x0000, &igot0); + SET(p, 0x0002, set2); + SET(p, 0x0000, set0); + return 0; +} +/****************************************************************************/ +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 + */ +/*--------------------------------------------------------------------------*/ +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 + */ +/*--------------------------------------------------------------------------*/ +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. + */ +/*--------------------------------------------------------------------------*/ +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; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * NOTE: THE FOLLOWING IS NOT CHECKED: + * REGISTER 0x0F, WHICH IS INVOLVED IN CHROMINANCE AUTOMATIC GAIN CONTROL. + */ +/*--------------------------------------------------------------------------*/ +int check_saa(struct usb_device *p, bool ntsc) +{ + int i, ir, rc = 0; + struct saa7113config const *cfg; + if (!p) + return -ENODEV; + + cfg = (ntsc) ? saa7113configNTSC : saa7113configPAL; + for (i = 0; cfg[i].reg != 0xFF; i++) { + if (0x0F == cfg[i].reg) + continue; + ir = read_saa(p, cfg[i].reg); + if (ir != cfg[i].set) { + SAY("SAA register 0x%02X has 0x%02X, expected 0x%02X\n", + cfg[i].reg, ir, cfg[i].set); + rc--; + } + } + + return (rc < -8) ? rc : 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; + else { + 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; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * NOTE: THE FOLLOWING ARE NOT CHECKED: + * REGISTERS 0x000, 0x002: FUNCTIONALITY IS NOT KNOWN + * REGISTER 0x100: ACCEPT ALSO (0x80 | stk1160config....[.].set) + */ +/*--------------------------------------------------------------------------*/ +int check_stk(struct usb_device *p, bool ntsc) +{ + int i, ir; + const struct stk1160config *cfg; + + if (!p) + return -ENODEV; + cfg = (ntsc) ? stk1160configNTSC : stk1160configPAL; + + for (i = 0; 0xFFF != cfg[i].reg; i++) { + if (0x000 == cfg[i].reg || 0x002 == cfg[i].reg) + continue; + + + ir = read_stk(p, cfg[i].reg); + if (0x100 == cfg[i].reg) { + if ((ir != (0xFF & cfg[i].set)) && + (ir != (0x80 | (0xFF & cfg[i].set))) && + (0xFFFF != cfg[i].set)) { + SAY("STK reg[0x%03X]=0x%02X expected 0x%02X\n", + cfg[i].reg, ir, cfg[i].set); + } + continue; + } + if ((ir != (0xFF & cfg[i].set)) && (0xFFFF != cfg[i].set)) + SAY("STK register 0x%03X has 0x%02X,expected 0x%02X\n", + cfg[i].reg, ir, cfg[i].set); + } + 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; +} +/****************************************************************************/ +int read_stk(struct usb_device *p, u32 reg0) +{ + u8 igot; + + if (!p) + return -ENODEV; + igot = 0; + GET(p, reg0, &igot); + return igot; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * HARDWARE USERSPACE INPUT NUMBER PHYSICAL INPUT DRIVER input VALUE + * + * CVBS+S-VIDEO 0 or 1 CVBS 1 + * FOUR-CVBS 0 or 1 CVBS1 1 + * FOUR-CVBS 2 CVBS2 2 + * FOUR-CVBS 3 CVBS3 3 + * FOUR-CVBS 4 CVBS4 4 + * CVBS+S-VIDEO 5 S-VIDEO 5 + * + * WHEN 5==input THE ARGUMENT mode MUST ALSO BE SUPPLIED: + * + * mode 7 => GAIN TO BE SET EXPLICITLY USING REGISTER 0x05 (UNTESTED) + * mode 9 => USE AUTOMATIC GAIN CONTROL (DEFAULT) + * +*/ +/*---------------------------------------------------------------------------*/ +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 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 +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 (0 != 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 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; +} +/*****************************************************************************/ +int audio_gainget(struct usb_device *pusb_device) +{ + int igot; + + if (!pusb_device) + return -ENODEV; + igot = read_vt(pusb_device, 0x001C); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x1C\n"); + return igot; +} +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_main.c b/drivers/staging/media/easycap/easycap_main.c new file mode 100644 index 000000000000..a45c0b507067 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_main.c @@ -0,0 +1,4253 @@ +/****************************************************************************** +* * +* 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); + +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 +} + +/*---------------------------------------------------------------------------*/ +/* + * PARAMETERS USED WHEN REGISTERING THE VIDEO INTERFACE + * + * NOTE: SOME KERNELS IGNORE usb_class_driver.minor_base, AS MENTIONED BY + * CORBET ET AL. "LINUX DEVICE DRIVERS", 3rd EDITION, PAGE 253. + * THIS IS THE CASE FOR OpenSUSE. + */ +/*---------------------------------------------------------------------------*/ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +/****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * THIS ROUTINE DOES NOT DETECT DUPLICATE OCCURRENCES OF POINTER peasycap +*/ +/*---------------------------------------------------------------------------*/ +int 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; + } else { + JOM(16, "peasycap->pusb_device=%p\n", peasycap->pusb_device); + } + file->private_data = peasycap; + rc = wakeup_device(peasycap->pusb_device); + if (0 == rc) + JOM(8, "wakeup_device() OK\n"); + else { + 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; + } + 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 = 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 +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; + kill_video_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) + submit_video_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 submit_video_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) { + JOM(4, "attempting cleanup instead of submitting\n"); + list_for_each(plist_head, (peasycap->purb_video_head)) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb) { + purb = pdata_urb->purb; + if (purb) + usb_kill_urb(purb); + } + } + peasycap->video_isoc_streaming = 0; + } else { + peasycap->video_isoc_streaming = 1; + JOM(4, "submitted %i video urbs\n", m); + } + } else { + JOM(4, "already streaming video urbs\n"); + } + return 0; +} +/*****************************************************************************/ +int kill_video_urbs(struct easycap *peasycap) +{ + int m; + struct list_head *plist_head; + struct data_urb *pdata_urb; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->video_isoc_streaming) { + JOM(8, "%i=video_isoc_streaming, no video urbs killed\n", + 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 (0 != kill_video_urbs(peasycap)) { + SAM("ERROR: kill_video_urbs() failed\n"); + return -EFAULT; + } + JOM(4, "ending successfully\n"); + return 0; +} +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +/*****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * 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; + struct data_urb *pdata_urb; + struct list_head *plist_head, *plist_next; + int k, m, gone, kd; + 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; + + peasycap = container_of(pkref, struct easycap, kref); + if (!peasycap) { + SAM("ERROR: peasycap is NULL: cannot perform deletions\n"); + return; + } + kd = isdongle(peasycap); +/*---------------------------------------------------------------------------*/ +/* + * FREE VIDEO. + */ +/*---------------------------------------------------------------------------*/ + if (peasycap->purb_video_head) { + JOM(4, "freeing 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) { + JOM(4, "ERROR: pdata_urb is NULL\n"); + } else { + if (pdata_urb->purb) { + usb_free_urb(pdata_urb->purb); + pdata_urb->purb = NULL; + peasycap->allocation_video_urb -= 1; + 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); + pdata_urb = NULL; + 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; + } +/*---------------------------------------------------------------------------*/ + 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)); +/*---------------------------------------------------------------------------*/ + 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); +/*---------------------------------------------------------------------------*/ + 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); +/*---------------------------------------------------------------------------*/ +/* + * FREE AUDIO. + */ +/*---------------------------------------------------------------------------*/ + 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) + JOM(4, "ERROR: pdata_urb is NULL\n"); + else { + if (pdata_urb->purb) { + usb_free_urb(pdata_urb->purb); + pdata_urb->purb = NULL; + peasycap->allocation_audio_urb -= 1; + 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); + pdata_urb = NULL; + 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; + } +/*---------------------------------------------------------------------------*/ + 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)); +/*---------------------------------------------------------------------------*/ + 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; + + 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"); + } + + 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); + + JOT(4, "ending.\n"); + return; +} +/*****************************************************************************/ +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 = 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 != 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_dqbuf(peasycap, 0); + peasycap->polled = 1; + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + if (0 == rc) + return POLLIN | POLLRDNORM; + else + return POLLERR; + } +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * IF mode IS NONZERO THIS ROUTINE RETURNS -EAGAIN RATHER THAN BLOCKING. + */ +/*---------------------------------------------------------------------------*/ +int easycap_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; + kill_video_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; + kill_video_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; + kill_video_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; + kill_video_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. + * + * THE BOOLEAN PARAMETER offerfields IS true ONLY WHEN THE USER PROGRAM + * CHOOSES THE OPTION V4L2_FIELD_INTERLACED. + */ +/*---------------------------------------------------------------------------*/ +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, offerfields, 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); + if (peasycap->offerfields) + JOM(8, "===== offerfields\n"); + +/*---------------------------------------------------------------------------*/ +/* + * 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*/ +/*---------------------------------------------------------------------------*/ + + offerfields = peasycap->offerfields; + 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. + */ +/*---------------------------------------------------------------------------*/ +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 const struct file_operations easycap_fops = { + .owner = THIS_MODULE, + .open = easycap_open, + .unlocked_ioctl = easycap_unlocked_ioctl, + .poll = easycap_poll, + .mmap = easycap_mmap, + .llseek = no_llseek, +}; +static const struct usb_class_driver easycap_class = { + .name = "usb/easycap%d", + .fops = &easycap_fops, + .minor_base = USB_SKEL_MINOR_BASE, +}; +/*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ +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, +}; +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * WHEN THE EasyCAP IS PHYSICALLY PLUGGED IN, THIS FUNCTION IS CALLED THREE + * TIMES, ONCE FOR EACH OF THE THREE INTERFACES. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +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 urb *purb; + struct easycap *peasycap; + int ndong; + struct data_urb *pdata_urb; + int i, j, k, m, rc; + u8 bInterfaceNumber; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + void *pbuf; + int okalt[8], isokalt; + int okepn[8]; + int okmps[8]; + int maxpacketsize; + u16 mask; + s32 value; + struct easycap_format *peasycap_format; + int fmtidx; + struct inputset *inputset; + + 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 EasyCAP WAS + * PHYSICALLY UNPLUGGED. + * + * THE POINTER peasycap TO THE struct easycap IS REMEMBERED WHEN + * INTERFACES 1 AND 2 ARE PROBED. +*/ +/*---------------------------------------------------------------------------*/ + if (0 == bInterfaceNumber) { + peasycap = kzalloc(sizeof(struct easycap), GFP_KERNEL); + if (!peasycap) { + SAY("ERROR: Could not allocate peasycap\n"); + return -ENOMEM; + } +/*---------------------------------------------------------------------------*/ +/* + * PERFORM URGENT INTIALIZATIONS ... +*/ +/*---------------------------------------------------------------------------*/ + 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); + + if (mutex_lock_interruptible(&mutex_dongle)) { + SAY("ERROR: cannot down mutex_dongle\n"); + return -ERESTARTSYS; + } else { +/*---------------------------------------------------------------------------*/ + /* + * FOR INTERFACES 1 AND 2 THE POINTER peasycap WILL NEED TO + * TO BE THE SAME AS THAT ALLOCATED NOW FOR INTERFACE 0. + * + * NORMALLY ndong WILL NOT HAVE CHANGED SINCE INTERFACE 0 WAS + * PROBED, BUT THIS MAY NOT BE THE CASE IF, FOR EXAMPLE, TWO + * EASYCAPs ARE PLUGGED IN SIMULTANEOUSLY. + */ +/*---------------------------------------------------------------------------*/ + for (ndong = 0; ndong < DONGLE_MANY; ndong++) { + if ((!easycapdc60_dongle[ndong].peasycap) && + (!mutex_is_locked(&easycapdc60_dongle + [ndong].mutex_video)) && + (!mutex_is_locked(&easycapdc60_dongle + [ndong].mutex_audio))) { + easycapdc60_dongle[ndong].peasycap = peasycap; + peasycap->isdongle = ndong; + JOM(8, "intf[%i]: peasycap-->easycap" + "_dongle[%i].peasycap\n", + bInterfaceNumber, ndong); + break; + } + } + if (DONGLE_MANY <= ndong) { + SAM("ERROR: too many dongles\n"); + mutex_unlock(&mutex_dongle); + return -ENOMEM; + } + mutex_unlock(&mutex_dongle); + } + peasycap->allocation_video_struct = sizeof(struct easycap); + peasycap->allocation_video_page = 0; + peasycap->allocation_video_urb = 0; + peasycap->allocation_audio_struct = 0; + peasycap->allocation_audio_page = 0; + peasycap->allocation_audio_urb = 0; + +/*---------------------------------------------------------------------------*/ +/* + * ... AND FURTHER INITIALIZE THE STRUCTURE +*/ +/*---------------------------------------------------------------------------*/ + peasycap->pusb_device = usbdev; + peasycap->pusb_interface = intf; + + peasycap->ilk = 0; + 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; + + for (k = 0; k < INPUT_MANY; k++) + peasycap->lost[k] = 0; + peasycap->skip = 0; + peasycap->skipped = 0; + peasycap->offerfields = 0; +/*---------------------------------------------------------------------------*/ +/* + * DYNAMICALLY FILL IN THE AVAILABLE FORMATS ... + */ +/*---------------------------------------------------------------------------*/ + rc = fillin_formats(); + if (0 > rc) { + SAM("ERROR: fillin_formats() rc = %i\n", rc); + return -EFAULT; + } + JOM(4, "%i formats available\n", rc); +/*---------------------------------------------------------------------------*/ +/* + * ... AND POPULATE easycap.inputset[] +*/ +/*---------------------------------------------------------------------------*/ + /* FIXME: maybe we just use memset 0 */ + inputset = peasycap->inputset; + for (k = 0; k < INPUT_MANY; k++) { + inputset[k].input_ok = 0; + inputset[k].standard_offset_ok = 0; + inputset[k].format_offset_ok = 0; + inputset[k].brightness_ok = 0; + inputset[k].contrast_ok = 0; + inputset[k].saturation_ok = 0; + inputset[k].hue_ok = 0; + } + + fmtidx = peasycap->ntsc ? NTSC_M : PAL_BGHIN; + m = 0; + mask = 0; + for (i = 0; 0xFFFF != easycap_standard[i].mask; 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 (1 != m) { + 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++) { + struct v4l2_pix_format *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 (1 != m) { + SAM("ERROR: inputset[]->format_offset unpopulated\n"); + return -ENOENT; + } + + m = 0; + for (i = 0; 0xFFFFFFFF != easycap_control[i].id; 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 (4 != m) { + SAM("ERROR: inputset[]->brightness underpopulated\n"); + return -ENOENT; + } + for (k = 0; k < INPUT_MANY; k++) + inputset[k].input = k; + JOM(4, "populated inputset[]\n"); + JOM(4, "finished initialization\n"); + } else { +/*---------------------------------------------------------------------------*/ +/* + * FIXME + * + * IDENTIFY THE APPROPRIATE POINTER peasycap FOR INTERFACES 1 AND 2. + * THE ADDRESS OF peasycap->pusb_device IS RELUCTANTLY USED FOR THIS PURPOSE. + */ +/*---------------------------------------------------------------------------*/ + for (ndong = 0; ndong < DONGLE_MANY; ndong++) { + if (usbdev == easycapdc60_dongle[ndong].peasycap-> + pusb_device) { + peasycap = easycapdc60_dongle[ndong].peasycap; + JOT(8, "intf[%i]: dongle[%i].peasycap\n", + bInterfaceNumber, ndong); + break; + } + } + if (DONGLE_MANY <= ndong) { + SAY("ERROR: peasycap is unknown when probing interface %i\n", + bInterfaceNumber); + return -ENODEV; + } + if (!peasycap) { + SAY("ERROR: peasycap is NULL when probing interface %i\n", + bInterfaceNumber); + return -ENODEV; + } + } +/*---------------------------------------------------------------------------*/ + 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); + } + } + } +/*---------------------------------------------------------------------------*/ +/* + * INVESTIGATE ALL ALTSETTINGS. + * 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) { +/*---------------------------------------------------------------------------*/ +/* + * INTERFACE 0 IS THE 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; + } else { + peasycap->video_altsetting_on = okalt[isokalt - 1]; + JOM(4, "%i=video_altsetting_on <====\n", + peasycap->video_altsetting_on); + } +/*---------------------------------------------------------------------------*/ +/* + * DECIDE THE 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); +/*---------------------------------------------------------------------------*/ + 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 (k = 0; k < FRAME_BUFFER_MANY; k++) { + for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->frame_buffer[k][m].pgo) + SAM("attempting to reallocate frame " + " buffers\n"); + else { + pbuf = (void *)__get_free_page(GFP_KERNEL); + if (!pbuf) { + SAM("ERROR: Could not allocate frame " + "buffer %i page %i\n", k, m); + return -ENOMEM; + } else + peasycap->allocation_video_page += 1; + peasycap->frame_buffer[k][m].pgo = pbuf; + } + peasycap->frame_buffer[k][m].pto = + peasycap->frame_buffer[k][m].pgo; + } + } + + peasycap->frame_fill = 0; + peasycap->frame_read = 0; + JOM(4, "allocation of frame buffers done: %i pages\n", k * + m); +/*---------------------------------------------------------------------------*/ + 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 (k = 0; k < FIELD_BUFFER_MANY; k++) { + for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->field_buffer[k][m].pgo) { + SAM("ERROR: attempting to reallocate " + "field buffers\n"); + } else { + pbuf = (void *) __get_free_page(GFP_KERNEL); + if (!pbuf) { + SAM("ERROR: Could not allocate field" + " buffer %i page %i\n", k, m); + return -ENOMEM; + } + else + peasycap->allocation_video_page += 1; + peasycap->field_buffer[k][m].pgo = pbuf; + } + peasycap->field_buffer[k][m].pto = + peasycap->field_buffer[k][m].pgo; + } + peasycap->field_buffer[k][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", k * + m); +/*---------------------------------------------------------------------------*/ + 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 (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) { + pbuf = (void *)__get_free_pages(GFP_KERNEL, + VIDEO_ISOC_ORDER); + if (!pbuf) { + SAM("ERROR: Could not allocate isoc video buffer " + "%i\n", k); + return -ENOMEM; + } else + peasycap->allocation_video_page += + BIT(VIDEO_ISOC_ORDER); + + peasycap->video_isoc_buffer[k].pgo = pbuf; + peasycap->video_isoc_buffer[k].pto = + pbuf + peasycap->video_isoc_buffer_size; + peasycap->video_isoc_buffer[k].kount = k; + } + JOM(4, "allocation of isoc video buffers done: %i pages\n", + k * (0x01 << VIDEO_ISOC_ORDER)); +/*---------------------------------------------------------------------------*/ +/* + * ALLOCATE AND INITIALIZE MULTIPLE struct urb ... + */ +/*---------------------------------------------------------------------------*/ + 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 (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) { + purb = usb_alloc_urb(peasycap->video_isoc_framesperdesc, + GFP_KERNEL); + if (!purb) { + SAM("ERROR: usb_alloc_urb returned NULL for buffer " + "%i\n", k); + return -ENOMEM; + } else + peasycap->allocation_video_urb += 1; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL); + if (!pdata_urb) { + SAM("ERROR: Could not allocate struct data_urb.\n"); + return -ENOMEM; + } else + peasycap->allocation_video_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_video_head); +/*---------------------------------------------------------------------------*/ +/* + * ... AND INITIALIZE THEM + */ +/*---------------------------------------------------------------------------*/ + if (!k) { + 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[k].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", k); +/*--------------------------------------------------------------------------*/ +/* + * 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 VERSIONS OF THE videodev MODULE + * CALL easycap_open() IMMEDIATELY AFTER REGISTRATION, CAUSING A CLASH. + * BEWARE. +*/ +/*---------------------------------------------------------------------------*/ + peasycap->ntsc = easycap_ntsc; + JOM(8, "defaulting initially to %s\n", + easycap_ntsc ? "NTSC" : "PAL"); + rc = reset(peasycap); + if (rc) { + SAM("ERROR: reset() rc = %i\n", rc); + return -EFAULT; + } +/*--------------------------------------------------------------------------*/ +/* + * THE VIDEO DEVICE CAN BE REGISTERED NOW, AS IT IS READY. + */ +/*--------------------------------------------------------------------------*/ + 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); +/*---------------------------------------------------------------------------*/ +/* + * 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))) { + err("Not able to register with videodev"); + videodev_release(&(peasycap->video_device)); + return -ENODEV; + } else { + (peasycap->registered_video)++; + SAM("registered with videodev: %i=minor\n", + peasycap->video_device.minor); + peasycap->minor = peasycap->video_device.minor; + } +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + break; + } +/*--------------------------------------------------------------------------*/ +/* + * INTERFACE 1 IS THE AUDIO CONTROL INTERFACE + * INTERFACE 2 IS THE AUDIO STREAMING INTERFACE + */ +/*--------------------------------------------------------------------------*/ + case 1: { + if (!peasycap) { + SAM("MISTAKE: peasycap is NULL\n"); + return -EFAULT; + } +/*--------------------------------------------------------------------------*/ +/* + * SAVE POINTER peasycap IN INTERFACE 1 + */ +/*--------------------------------------------------------------------------*/ + usb_set_intfdata(intf, peasycap); + JOM(4, "no initialization required for interface %i\n", + interface->bInterfaceNumber); + break; + } +/*--------------------------------------------------------------------------*/ + case 2: { + if (!peasycap) { + SAM("MISTAKE: peasycap is NULL\n"); + return -EFAULT; + } + if (!isokalt) { + SAM("ERROR: no viable audio_altsetting_on\n"); + return -ENOENT; + } else { + 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); + +/*---------------------------------------------------------------------------*/ + 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; + } else + 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"); +/*---------------------------------------------------------------------------*/ +/* + * ALLOCATE AND INITIALIZE MULTIPLE struct urb ... + */ +/*---------------------------------------------------------------------------*/ + 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) { + 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); +/*---------------------------------------------------------------------------*/ +/* + * ... AND INITIALIZE THEM + */ +/*---------------------------------------------------------------------------*/ + 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); +/*---------------------------------------------------------------------------*/ +/* + * SAVE POINTER peasycap IN THIS INTERFACE. + */ +/*---------------------------------------------------------------------------*/ + usb_set_intfdata(intf, peasycap); +/*---------------------------------------------------------------------------*/ +/* + * THE AUDIO DEVICE CAN BE REGISTERED NOW, AS IT IS READY. + */ +/*---------------------------------------------------------------------------*/ + JOM(4, "initializing ALSA card\n"); + + rc = easycap_alsa_probe(peasycap); + if (rc) { + err("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 AND 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 EasyCAP HAS ALREADY BEEN PHYSICALLY + * UNPLUGGED. HENCE peasycap->pusb_device IS NO LONGER VALID. + * + * THIS FUNCTION AFFECTS ALSA. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +static void easycap_usb_disconnect(struct usb_interface *pusb_interface) +{ + struct usb_host_interface *pusb_host_interface; + struct usb_interface_descriptor *pusb_interface_descriptor; + u8 bInterfaceNumber; + struct easycap *peasycap; + + struct list_head *plist_head; + struct data_urb *pdata_urb; + int minor, m, kd; + + 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); + + if (1 == bInterfaceNumber) + return; + + peasycap = usb_get_intfdata(pusb_interface); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } +/*---------------------------------------------------------------------------*/ +/* + * IF THE WAIT QUEUES ARE NOT CLEARED A DEADLOCK IS POSSIBLE. BEWARE. +*/ +/*---------------------------------------------------------------------------*/ + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + wake_up_interruptible(&(peasycap->wq_video)); + wake_up_interruptible(&(peasycap->wq_audio)); +/*---------------------------------------------------------------------------*/ + switch (bInterfaceNumber) { + case 0: { + if (peasycap->purb_video_head) { + 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) { + if (pdata_urb->purb) { + usb_kill_urb(pdata_urb->purb); + m++; + } + } + } + JOM(4, "%i video urbs killed\n", m); + } + break; + } +/*---------------------------------------------------------------------------*/ + case 2: { + if (peasycap->purb_audio_head) { + JOM(4, "killing 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) { + if (pdata_urb->purb) { + usb_kill_urb(pdata_urb->purb); + m++; + } + } + } + JOM(4, "%i audio urbs killed\n", m); + } + break; + } + default: + break; + } +/*--------------------------------------------------------------------------*/ +/* + * DEREGISTER + * + * THIS PROCEDURE WILL BLOCK UNTIL easycap_poll(), VIDEO IOCTL 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. BEWARE. + */ +/*--------------------------------------------------------------------------*/ + kd = 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; + } +/*---------------------------------------------------------------------------*/ +/* + * CALL easycap_delete() IF NO REMAINING REFERENCES TO peasycap + * (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; +} +/*****************************************************************************/ + +/*---------------------------------------------------------------------------*/ +/* + * PARAMETERS APPLICABLE TO ENTIRE DRIVER, I.E. BOTH VIDEO AND AUDIO + */ +/*---------------------------------------------------------------------------*/ +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); +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); + +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_settings.c b/drivers/staging/media/easycap/easycap_settings.c new file mode 100644 index 000000000000..70f59b13c34d --- /dev/null +++ b/drivers/staging/media/easycap/easycap_settings.c @@ -0,0 +1,696 @@ +/****************************************************************************** +* * +* 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 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 + } +}; +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_sound.c b/drivers/staging/media/easycap/easycap_sound.c new file mode 100644 index 000000000000..b22bb39b5f69 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_sound.c @@ -0,0 +1,816 @@ +/****************************************************************************** +* * +* 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, +}; + + +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * 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; + int retval; + + 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: + retval = -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; +} + +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * COMMON AUDIO INITIALIZATION + */ +/*---------------------------------------------------------------------------*/ +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 = 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 = wakeup_device(peasycap->pusb_device); + JOM(8, "wakeup_device() returned %i\n", rc); + + peasycap->audio_eof = 0; + peasycap->audio_idle = 0; + + submit_audio_urbs(peasycap); + + JOM(4, "finished initialization\n"); + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * SUBMIT ALL AUDIO URBS. + */ +/*---------------------------------------------------------------------------*/ +int +submit_audio_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_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) { + JOM(4, "attempting cleanup instead of submitting\n"); + 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); + } + peasycap->audio_isoc_streaming = 0; + } else { + peasycap->audio_isoc_streaming = m; + JOM(4, "submitted %i audio urbs\n", m); + } + + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * KILL ALL AUDIO URBS. + */ +/*---------------------------------------------------------------------------*/ +int +kill_audio_urbs(struct easycap *peasycap) +{ + int m; + struct list_head *plist_head; + struct data_urb *pdata_urb; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + + if (!peasycap->audio_isoc_streaming) { + JOM(8, "%i=audio_isoc_streaming, no audio urbs killed\n", + 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; + JOM(4, "killing 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_kill_urb(pdata_urb->purb); + m++; + } + } + JOM(4, "%i audio urbs killed\n", m); + + return 0; +} +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_testcard.c b/drivers/staging/media/easycap/easycap_testcard.c new file mode 100644 index 000000000000..0f71470ace39 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_testcard.c @@ -0,0 +1,155 @@ +/****************************************************************************** +* * +* 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; +} |