Commit ed5a94a4 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge greg@deskfan:linux/BK/usb-new_drivers-2.6

into kroah.com:/home/greg/linux/BK/usb-new_drivers-2.6
parents 90d0999c 2417e1a8
......@@ -2669,6 +2669,13 @@ S: Kasarmikatu 11 A4
S: 70110 Kuopio
S: Finland
N: Luca Risolia
E: luca_ing@libero.it
D: V4L driver for W996[87]CF JPEG USB Dual Mode Camera Chip
S: Via Libertà 41/a
S: Osio Sotto, 24046, Bergamo
S: Italy
N: William E. Roadcap
E: roadcapw@cfw.com
W: http://www.cfw.com/~roadcapw
......@@ -3569,4 +3576,4 @@ S: France
# alphabetically. Leonard used to be very proud of being the
# last entry, and he'll get positively pissed if he can't even
# be second-to-last. (and this file really _is_ supposed to be
# in alphabetic order)
# in alphabetic order)
W996[87]CF JPEG USB Dual Mode Camera Chip driver for Linux 2.6
==============================================================
- Documentation -
Index
=====
1. Copyright
2. License
3. Overview
4. Supported devices
5. Kernel configuration and third-part module compilation
6. Module loading
7. Module paramaters
8. Credits
1. Copyright
============
Copyright (C) 2002 2003 by Luca Risolia <luca_ing@libero.it>
2. License
==========
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
3. Overview
===========
This driver supports the video streaming capabilities of the devices mounting
Winbond W9967CF and Winbond W9968CF JPEG USB Dual Mode Camera Chips, when they
are being commanded by USB.
The driver relies on the Video4Linux, USB and I2C core modules of the Linux
kernel, version 2.6.0 or greater, and is not compatible in any way with
previous versions. It has been designed to run properly on SMP systems
as well. At the moment, an additional module, "ovcamchip", is mandatory; it
provides support for some OmniVision CMOS sensors connected to the W996[87]CF
chips.
The driver is split into two modules: the basic one, "w9968cf", is needed for
the supported devices to work; the second one, "w9968cf-vpp", is an optional
module, which provides some useful video post-processing functions like video
decoding, up-scaling and colour conversions. These routines can't be included
into official kernels for performance purposes. Once the driver is installed,
every time an application tries to open a recognized device, "w9968cf" checks
the presence of the "w9968cf-vpp" module and loads it automatically by default.
Up to 32 cameras can be handled at the same time. They can be connected and
disconnected from the host many times without turning off the computer, if
your system supports the hotplug facility.
To change the default settings for each camera, many paramaters can be passed
through command line when the module is loaded into memory.
The latest and full featured version of the W996[87]CF driver can be found at:
http://go.lamarinapunto.com/
The "ovcamchip" module is part of the OV511 driver, version 2.25, which can be
downloaded from internet:
http://alpha.dyndns.org/ov511/
To know how to patch, compile and load it, read the paragraphs below.
4. Supported devices
====================
At the moment, known W996[87]CF based devices are:
- Aroma Digi Pen ADG-5000 Refurbished
- AVerTV USB
- Creative Labs Video Blaster WebCam Go
- Creative Labs Video Blaster WebCam Go Plus
- Die Lebon LDC-D35A Digital Kamera
- Ezonics EZ-802 EZMega Cam
- OPCOM Digi Pen VGA Dual Mode Pen Camera
If you know any other W996[87]CF based cameras, please contact me.
The list above does NOT imply that all those devices work with this driver: up
until now only webcams that have a CMOS sensor supported by the "ovcamchip"
module work.
For a list of supported CMOS sensors, please visit the module author homepage:
http://alpha.dyndns.org/ov511/
Possible external microcontrollers of those webcams are not supported: this
means that still images can't be downloaded from the device memory.
Furthermore, it's worth to note that I was only able to run tests on my
"Creative Labs Video Blaster WebCam Go". Donations of other models, for
additional testing and full support, would be much appreciated.
5. Kernel configuration and third-part module compilation
=========================================================
As noted above, kernel 2.6.0 is the minimum for this driver; for it to work
properly, the driver needs kernel support for Video4Linux, USB and I2C, and a
third-part module for the CMOS sensor.
The following options of the kernel configuration file must be enabled and
corresponding modules must be compiled:
# Multimedia devices
#
CONFIG_VIDEO_DEV=m
# I2C support
#
CONFIG_I2C=m
The I2C core module can be compiled statically in the kernel as well.
# USB support
#
CONFIG_USB=m
In addition, depending on the hardware being used, just one of the modules
below is necessary:
# USB Host Controller Drivers
#
CONFIG_USB_EHCI_HCD=m
CONFIG_USB_UHCI_HCD=m
CONFIG_USB_OHCI_HCD=m
Also, make sure "Enforce bandwidth allocation" is NOT enabled.
# USB Multimedia devices
#
CONFIG_USB_W9968CF=m
The last module we need is "ovcamchip.o". To obtain it, you have to download
the OV511 driver, version 2.25 - don't use other versions - which is available
at http://alpha.dyndns.org/ov511/ . Then you have to download the latest
version of the full featured W996[87]CF driver, which contains a patch for the
"ovcamchip" module; it is available at http://go.lamarinapunto.com .
Once you have obtained the packages, decompress, patch and compile the
"ovcamchip" module. In other words:
[user@localhost home]$ tar xvzf w9968cf-x.x.tar.gz
[user@localhost home]$ tar xvjf ov511-2.25.tar.bz2
[user@localhost home]$ cd ov511-2.25
[user@localhost ov511-2.25]$ patch -p1 < \
/path/to/w9968cf-x.x/ov511-2.25.patch
[user@localhost ov511-2.25]$ make
It's worth to note that the full featured version of the W996[87]CF driver
can also be installed overwriting the one in the kernel; in this case, read the
documentation included in the package.
If everything went well, the W996[87]CF driver can be immediatly used (see next
paragraph).
6. Module loading
=================
To use the driver, it is necessary to load the "w9968cf" module into memory
after every other module required.
For example, loading can be done this way, as root:
[root@localhost home]# modprobe usbcore
[root@localhost home]# modprobe i2c-core
[root@localhost ov511-x.xx]# insmod ./ovcamchip.ko
[root@localhost home]# modprobe w9968cf
At this point the devices should be recognized: "dmesg" can be used to analyze
kernel messages:
[user@localhost home]$ dmesg
There are a lot of parameters the module can use to change the default
settings for each device. To list every possible parameter with a brief
explanation about them and which syntax to use, it is recommended to run the
"modinfo" command:
[root@locahost home]# modinfo w9968cf
7. Module paramaters
====================
Module paramaters are listed below:
-------------------------------------------------------------------------------
Name: vppmod_load
Type: int
Syntax: <0|1>
Description: Automatic 'w9968cf-vpp' module loading: 0 disabled, 1 enabled.
If enabled, every time an application attempts to open a
camera, 'insmod' searches for the video post-processing module
in the system and loads it automatically (if present).
The 'w9968cf-vpp' module adds extra image manipulation
capabilities to the 'w9968cf' module,like software up-scaling,
colour conversions and video decoding.
Default: 1
-------------------------------------------------------------------------------
Name: simcams
Type: int
Syntax: <n>
Description: Number of cameras allowed to stream simultaneously.
n may vary from 0 to 32.
Default: 32
-------------------------------------------------------------------------------
Name: video_nr
Type: int array (min = 0, max = 32)
Syntax: <-1|n[,...]>
Description: Specify V4L minor mode number.
-1 = use next available
n = use minor number n
You can specify 32 cameras this way.
For example:
video_nr=-1,2,-1 would assign minor number 2 to the second
recognized camera and use auto for the first one and for every
other camera.
Default: -1
-------------------------------------------------------------------------------
Name: packet_size
Type: int array (min = 0, max = 32)
Syntax: <n[,...]>
Description: Specify the maximum data payload size in bytes for alternate
settings, for each device. n is scaled between 63 and 1023.
Default: 1023
-------------------------------------------------------------------------------
Name: max_buffers
Type: int array (min = 0, max = 32)
Syntax: <n[,...]>
Description: Only for advanced users.
Specify the maximum number of video frame buffers to allocate
for each device, from 2 to 32.
Default: 2
-------------------------------------------------------------------------------
Name: double_buffer
Type: int array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: Hardware double buffering: 0 disabled, 1 enabled.
It should be enabled if you want smooth video output: if you
obtain out of sync. video, disable it at all, or try to
decrease the 'clockdiv' module paramater value.
Default: 1 for every device.
-------------------------------------------------------------------------------
Name: clamping
Type: int array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: Video data clamping: 0 disabled, 1 enabled.
Default: 0 for every device.
-------------------------------------------------------------------------------
Name: filter_type
Type: int array (min = 0, max = 32)
Syntax: <0|1|2[,...]>
Description: Video filter type.
0 none, 1 (1-2-1) 3-tap filter, 2 (2-3-6-3-2) 5-tap filter.
The filter is used to reduce noise and aliasing artifacts
produced by the CCD or CMOS sensor.
Default: 0 for every device.
-------------------------------------------------------------------------------
Name: largeview
Type: int array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: Large view: 0 disabled, 1 enabled.
Default: 1 for every device.
-------------------------------------------------------------------------------
Name: upscaling
Type: int array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: Software scaling (for non-compressed video only):
0 disabled, 1 enabled.
Disable it if you have a slow CPU or you don't have enough
memory.
Default: 0 for every device.
Note: If 'w9968cf-vpp' is not loaded, this paramater is set to 0.
-------------------------------------------------------------------------------
Name: decompression
Type: int array (min = 0, max = 32)
Syntax: <0|1|2[,...]>
Description: Software video decompression:
0 = disables decompression
(doesn't allow formats needing decompression).
1 = forces decompression
(allows formats needing decompression only).
2 = allows any permitted formats.
Formats supporting (de)compressed video are YUV422P and
YUV420P/YUV420 in any resolutions where width and height are
multiples of 16.
Default: 2 for every device.
Note: If 'w9968cf-vpp' is not loaded, forcing decompression is not
allowed; in this case this paramater is set to 2.
-------------------------------------------------------------------------------
Name: force_palette
Type: int array (min = 0, max = 32)
Syntax: <0|9|10|13|15|8|7|1|6|3|4|5[,...]>
Description: Force picture palette.
In order:
0 = Off - allows any of the following formats:
9 = UYVY 16 bpp - Original video, compression disabled
10 = YUV420 12 bpp - Original video, compression enabled
13 = YUV422P 16 bpp - Original video, compression enabled
15 = YUV420P 12 bpp - Original video, compression enabled
8 = YUVY 16 bpp - Software conversion from UYVY
7 = YUV422 16 bpp - Software conversion from UYVY
1 = GREY 8 bpp - Software conversion from UYVY
6 = RGB555 16 bpp - Software conversion from UYVY
3 = RGB565 16 bpp - Software conversion from UYVY
4 = RGB24 24 bpp - Software conversion from UYVY
5 = RGB32 32 bpp - Software conversion from UYVY
When not 0, this paramater will override 'decompression'.
Default: 0 for every device. Initial palette is 9 (UYVY).
Note: If 'w9968cf-vpp' is not loaded, this paramater is set to 9.
-------------------------------------------------------------------------------
Name: force_rgb
Type: int array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: Read RGB video data instead of BGR:
1 = use RGB component ordering.
0 = use BGR component ordering.
This parameter has effect when using RGBX palettes only.
Default: 0 for every device.
-------------------------------------------------------------------------------
Name: autobright
Type: long array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: CMOS sensor automatically changes brightness:
0 = no, 1 = yes
Default: 0 for every device.
-------------------------------------------------------------------------------
Name: autoexp
Type: long array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: CMOS sensor automatically changes exposure:
0 = no, 1 = yes
Default: 1 for every device.
-------------------------------------------------------------------------------
Name: lightfreq
Type: long array (min = 0, max = 32)
Syntax: <50|60[,...]>
Description: Light frequency in Hz:
50 for European and Asian lighting, 60 for American lighting.
Default: 50 for every device.
-------------------------------------------------------------------------------
Name: bandingfilter
Type: long array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: Banding filter to reduce effects of fluorescent
lighting:
0 disabled, 1 enabled.
This filter tries to reduce the pattern of horizontal
light/dark bands caused by some (usually fluorescent) lighting.
Default: 0 for every device.
-------------------------------------------------------------------------------
Name: clockdiv
Type: long array (min = 0, max = 32)
Syntax: <-1|n[,...]>
Description: Force pixel clock divisor to a specific value (for experts):
n may vary from 0 to 127.
-1 for automatic value.
See also the 'double_buffer' module paramater.
Default: -1 for every device.
-------------------------------------------------------------------------------
Name: backlight
Type: long array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: Objects are lit from behind:
0 = no, 1 = yes
Default: 0 for every device.
-------------------------------------------------------------------------------
Name: mirror
Type: long array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: Reverse image horizontally:
0 = no, 1 = yes
Default: 0 for every device.
-------------------------------------------------------------------------------
Name: sensor_mono
Type: long array (min = 0, max = 32)
Syntax: <0|1[,...]>
Description: The CMOS sensor is monochrome:
0 = no, 1 = yes
Default: 0 for every device.
-------------------------------------------------------------------------------
Name: brightness
Type: long array (min = 0, max = 32)
Syntax: <n[,...]>
Description: Set picture brightness (0-65535).
This parameter has no effect if 'autobright' is enabled.
Default: 31000 for every device.
-------------------------------------------------------------------------------
Name: hue
Type: long array (min = 0, max = 32)
Syntax: <n[,...]>
Description: Set picture hue (0-65535).
Default: 32768 for every device.
-------------------------------------------------------------------------------
Name: colour
Type: long array (min = 0, max = 32)
Syntax: <n[,...]>
Description: Set picture saturation (0-65535).
Default: 32768 for every device.
-------------------------------------------------------------------------------
Name: contrast
Type: long array (min = 0, max = 32)
Syntax: <n[,...]>
Description: Set picture contrast (0-65535).
Default: 50000 for every device.
-------------------------------------------------------------------------------
Name: whiteness
Type: long array (min = 0, max = 32)
Syntax: <n[,...]>
Description: Set picture whiteness (0-65535).
Default: 32768 for every device.
-------------------------------------------------------------------------------
Name: debug
Type: int
Syntax: <n>
Description: Debugging information level, from 0 to 6:
0 = none (be cautious)
1 = critical errors
2 = significant informations
3 = configuration or general messages
4 = warnings
5 = called functions
6 = function internals
Level 5 and 6 are useful for testing only, when just one
device is used.
Default: 2
-------------------------------------------------------------------------------
Name: specific_debug
Type: int
Syntax: <0|1>
Description: Enable or disable specific debugging messages:
0 = print messages concerning every level <= 'debug' level.
1 = print messages concerning the level indicated by 'debug'.
Default: 0
-------------------------------------------------------------------------------
8. Credits
==========
The development would not have proceed much further without having looked at
the source code of other drivers and without the help of several persons; in
particular:
- the I2C interface to kernel and high-level CMOS sensor control routines have
been taken from the OV511 driver by Mark McClelland;
- memory management code has been copied from the bttv driver by Ralph Metzler,
Marcus Metzler and Gerd Knorr;
- the low-level I2C read function has been written by Frédéric Jouault, who
also gave me commented logs about sniffed USB traffic taken from another
driver for another system;
- the low-level I2C fast write function has been written by Piotr Czerczak;
......@@ -2212,6 +2212,13 @@ M: dbrownell@users.sourceforge.net
L: linux-usb-devel@lists.sourceforge.net
S: Maintained
USB W996[87]CF DRIVER
P: Luca Risolia
M: luca_ing@libero.it
L: linux-usb-devel@lists.sourceforge.net
W: http://go.lamarinapunto.com
S: Maintained
USER-MODE LINUX
P: Jeff Dike
M: jdike@karaya.com
......
......@@ -34,6 +34,7 @@ obj-$(CONFIG_USB_PWC) += media/
obj-$(CONFIG_USB_SE401) += media/
obj-$(CONFIG_USB_STV680) += media/
obj-$(CONFIG_USB_VICAM) += media/
obj-$(CONFIG_USB_W9968CF) += media/
obj-$(CONFIG_USB_CATC) += net/
obj-$(CONFIG_USB_KAWETH) += net/
......@@ -58,4 +59,3 @@ obj-$(CONFIG_USB_SPEEDTOUCH) += misc/
obj-$(CONFIG_USB_TEST) += misc/
obj-$(CONFIG_USB_TIGL) += misc/
obj-$(CONFIG_USB_USS720) += misc/
......@@ -991,6 +991,7 @@ int usb_new_device(struct usb_device *dev, struct device *parent)
int err = -EINVAL;
int i;
int j;
int config;
/*
* Set the driver for the usb device to point to the "generic" driver.
......@@ -1108,18 +1109,30 @@ int usb_new_device(struct usb_device *dev, struct device *parent)
/* choose and set the configuration. that registers the interfaces
* with the driver core, and lets usb device drivers bind to them.
* NOTE: should interact with hub power budgeting.
*/
config = dev->config[0].desc.bConfigurationValue;
if (dev->descriptor.bNumConfigurations != 1) {
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
/* heuristic: Linux is more likely to have class
* drivers, so avoid vendor-specific interfaces.
*/
if (dev->config[i].interface[0]->altsetting
->desc.bInterfaceClass
== USB_CLASS_VENDOR_SPEC)
continue;
config = dev->config[i].desc.bConfigurationValue;
break;
}
dev_info(&dev->dev,
"configuration #%d chosen from %d choices\n",
dev->config[0].desc.bConfigurationValue,
config,
dev->descriptor.bNumConfigurations);
}
err = usb_set_configuration(dev,
dev->config[0].desc.bConfigurationValue);
err = usb_set_configuration(dev, config);
if (err) {
dev_err(&dev->dev, "can't set config #%d, error %d\n",
dev->config[0].desc.bConfigurationValue, err);
config, err);
goto fail;
}
......
......@@ -177,3 +177,30 @@ config USB_STV680
To compile this driver as a module, choose M here: the
module will be called stv680.
config USB_W9968CF
tristate "USB W996[87]CF JPEG Dual Mode Camera support"
depends on USB && VIDEO_DEV && I2C
---help---
Say Y here if you want support for cameras based on
Winbond W9967CF/W9968CF JPEG USB Dual Mode Camera Chips.
This driver has an optional plugin, which is distributed as a
separate module only (released under GPL). It contains code that
allows you to use higher resolutions and framerates, and can't
be included into the official Linux kernel for performance
purposes.
At the moment the driver needs a third-part module for the CMOS
sensors, which is available on internet: it is recommended to read
<file:Documentation/usb/w9968cf.txt> for more informations and for
a list of supported cameras.
This driver uses the Video For Linux and the I2C APIs.
You must say Y or M to both "Video For Linux" and
"I2C Support" to use this driver.
Information on this API and pointers to "v4l" programs may be found
on the WWW at <http://roadrunner.swansea.uk.linux.org/v4l.shtml>.
This code is also available as a module ( = code which can be
inserted in and removed from the running kernel whenever you want).
The module will be called w9968cf.o. If you want to compile it as a
module, say M here and read <file:Documentation/modules.txt>.
......@@ -13,3 +13,4 @@ obj-$(CONFIG_USB_PWC) += pwc.o
obj-$(CONFIG_USB_SE401) += se401.o
obj-$(CONFIG_USB_STV680) += stv680.o
obj-$(CONFIG_USB_VICAM) += vicam.o usbvideo.o
obj-$(CONFIG_USB_W9968CF) += w9968cf.o
This source diff could not be displayed because it is too large. You can view the blob instead.
/***************************************************************************
* Video4Linux driver for W996[87]CF JPEG USB Dual Mode Camera Chip. *
* *
* Copyright (C) 2002 2003 by Luca Risolia <luca_ing@libero.it> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
***************************************************************************/
#ifndef _W9968CF_H_
#define _W9968CF_H_
#include <linux/videodev.h>
#include <linux/usb.h>
#include <linux/i2c.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/config.h>
#include <asm/semaphore.h>
#include <asm/types.h>
#include "w9968cf_externaldef.h"
/****************************************************************************
* Default values *
****************************************************************************/
#define W9968CF_VPPMOD_LOAD 1 /* automatic 'w9968cf-vpp' module loading */
/* Comment/uncomment the following line to enable/disable debugging messages */
#define W9968CF_DEBUG
/* These have effect only if W9968CF_DEBUG is defined */
#define W9968CF_DEBUG_LEVEL 2 /* from 0 to 6. 0 for no debug informations */
#define W9968CF_SPECIFIC_DEBUG 0 /* 0 or 1 */
#define W9968CF_MAX_DEVICES 32
#define W9968CF_SIMCAMS W9968CF_MAX_DEVICES /* simultaneous cameras */
#define W9968CF_MAX_BUFFERS 32
#define W9968CF_BUFFERS 2 /* n. of frame buffers from 2 to MAX_BUFFERS */
/* Maximum data payload sizes in bytes for alternate settings */
static const u16 wMaxPacketSize[] = {1023, 959, 895, 831, 767, 703, 639, 575,
511, 447, 383, 319, 255, 191, 127, 63};
#define W9968CF_PACKET_SIZE 1023 /* according to wMaxPacketSizes[] */
#define W9968CF_MIN_PACKET_SIZE 63 /* minimum value */
#define W9968CF_ISO_PACKETS 5 /* n.of packets for isochronous transfers */
#define W9968CF_USB_CTRL_TIMEOUT HZ /* timeout for usb control commands */
#define W9968CF_URBS 2 /* n. of scheduled URBs for ISO transfer */
#define W9968CF_I2C_BUS_DELAY 4 /* delay in us for I2C bit r/w operations */
#define W9968CF_I2C_RW_RETRIES 15 /* number of max I2C r/w retries */
/* Available video formats */
struct w9968cf_format {
const u16 palette;
const u16 depth;
const u8 compression;
};
static const struct w9968cf_format w9968cf_formatlist[] = {
{ VIDEO_PALETTE_UYVY, 16, 0 }, /* original video */
{ VIDEO_PALETTE_YUV422P, 16, 1 }, /* with JPEG compression */
{ VIDEO_PALETTE_YUV420P, 12, 1 }, /* with JPEG compression */
{ VIDEO_PALETTE_YUV420, 12, 1 }, /* same as YUV420P */
{ VIDEO_PALETTE_YUYV, 16, 0 }, /* software conversion */
{ VIDEO_PALETTE_YUV422, 16, 0 }, /* software conversion */
{ VIDEO_PALETTE_GREY, 8, 0 }, /* software conversion */
{ VIDEO_PALETTE_RGB555, 16, 0 }, /* software conversion */
{ VIDEO_PALETTE_RGB565, 16, 0 }, /* software conversion */
{ VIDEO_PALETTE_RGB24, 24, 0 }, /* software conversion */
{ VIDEO_PALETTE_RGB32, 32, 0 }, /* software conversion */
{ 0, 0, 0 } /* 0 is a terminating entry */
};
#define W9968CF_DECOMPRESSION 2 /* decomp:0=disable,1=force,2=any formats */
#define W9968CF_PALETTE_DECOMP_OFF VIDEO_PALETTE_UYVY /* when decomp=0 */
#define W9968CF_PALETTE_DECOMP_FORCE VIDEO_PALETTE_YUV420P /* when decomp=1 */
#define W9968CF_PALETTE_DECOMP_ON VIDEO_PALETTE_UYVY /* when decomp=2 */
#define W9968CF_FORCE_RGB 0 /* read RGB instead of BGR, yes=1/no=0 */
#define W9968CF_MAX_WIDTH 800 /* should be >= 640 */
#define W9968CF_MAX_HEIGHT 600 /* should be >= 480 */
#define W9968CF_WIDTH 320 /* from 128 to 352, multiple of 16 */
#define W9968CF_HEIGHT 240 /* from 96 to 288, multiple of 16 */
#define W9968CF_CLAMPING 0 /* 0 disable, 1 enable video data clamping */
#define W9968CF_FILTER_TYPE 0 /* 0 disable 1 (1-2-1), 2 (2-3-6-3-2) */
#define W9968CF_DOUBLE_BUFFER 1 /* 0 disable, 1 enable double buffer */
#define W9968CF_LARGEVIEW 1 /* 0 disable, 1 enable */
#define W9968CF_UPSCALING 0 /* 0 disable, 1 enable */
#define W9968CF_SENSOR_MONO 0 /* 0 not monochrome, 1 monochrome sensor */
#define W9968CF_BRIGHTNESS 31000 /* from 0 to 65535 */
#define W9968CF_HUE 32768 /* from 0 to 65535 */
#define W9968CF_COLOUR 32768 /* from 0 to 65535 */
#define W9968CF_CONTRAST 50000 /* from 0 to 65535 */
#define W9968CF_WHITENESS 32768 /* from 0 to 65535 */
#define W9968CF_AUTOBRIGHT 0 /* 0 disable, 1 enable automatic brightness */
#define W9968CF_AUTOEXP 1 /* 0 disable, 1 enable automatic exposure */
#define W9968CF_LIGHTFREQ 50 /* light frequency. 50Hz (Europe) or 60Hz */
#define W9968CF_BANDINGFILTER 0 /* 0 disable, 1 enable banding filter */
#define W9968CF_BACKLIGHT 0 /* 0 or 1, 1=object is lit from behind */
#define W9968CF_MIRROR 0 /* 0 or 1 [don't] reverse image horizontally*/
#define W9968CF_CLOCKDIV -1 /* -1 = automatic clock divisor */
#define W9968CF_DEF_CLOCKDIVISOR 0 /* default sensor clock divisor value */
/****************************************************************************
* Globals *
****************************************************************************/
#define W9968CF_MODULE_NAME "V4L driver for W996[87]CF JPEG USB " \
"Dual Mode Camera Chip"
#define W9968CF_MODULE_VERSION "v1.22"
#define W9968CF_MODULE_AUTHOR "(C) 2002 2003 Luca Risolia"
#define W9968CF_AUTHOR_EMAIL "<luca_ing@libero.it>"
static u8 w9968cf_vppmod_present; /* status flag: yes=1, no=0 */
static const struct usb_device_id winbond_id_table[] = {
{
/* Creative Labs Video Blaster WebCam Go Plus */
USB_DEVICE(0x041e, 0x4003),
.driver_info = (unsigned long)"w9968cf",
},
{
/* Generic W996[87]CF JPEG USB Dual Mode Camera */
USB_DEVICE(0x1046, 0x9967),
.driver_info = (unsigned long)"w9968cf",
},
{ } /* terminating entry */
};
MODULE_DEVICE_TABLE(usb, winbond_id_table);
/* W996[87]CF camera models, internal ids: */
enum w9968cf_model_id {
W9968CF_MOD_GENERIC = 1, /* Generic W996[87]CF based device */
W9968CF_MOD_CLVBWGP = 11,/*Creative Labs Video Blaster WebCam Go Plus*/
W9968CF_MOD_ADPA5R = 21, /* Aroma Digi Pen ADG-5000 Refurbished */
W9986CF_MOD_AU = 31, /* AVerTV USB */
W9968CF_MOD_CLVBWG = 34, /* Creative Labs Video Blaster WebCam Go */
W9968CF_MOD_DLLDK = 37, /* Die Lebon LDC-D35A Digital Kamera */
W9968CF_MOD_EEEMC = 40, /* Ezonics EZ-802 EZMega Cam */
W9968CF_MOD_ODPVDMPC = 43,/* OPCOM Digi Pen VGA Dual Mode Pen Camera */
};
enum w9968cf_frame_status {
F_READY, /* finished grabbing & ready to be read/synced */
F_GRABBING, /* in the process of being grabbed into */
F_ERROR, /* something bad happened while processing */
F_UNUSED /* unused (no VIDIOCMCAPTURE) */
};
struct w9968cf_frame_t {
void* buffer;
u32 length;
enum w9968cf_frame_status status;
struct w9968cf_frame_t* next;
u8 queued;
};
enum w9968cf_vpp_flag {
VPP_NONE = 0x00,
VPP_UPSCALE = 0x01,
VPP_SWAP_YUV_BYTES = 0x02,
VPP_DECOMPRESSION = 0x04,
VPP_UYVY_TO_RGBX = 0x08,
};
struct list_head w9968cf_dev_list; /* head of V4L registered cameras list */
LIST_HEAD(w9968cf_dev_list);
struct semaphore w9968cf_devlist_sem; /* semaphore for list traversal */
/* Main device driver structure */
struct w9968cf_device {
enum w9968cf_model_id id; /* private device identifier */
struct video_device v4ldev; /* V4L structure */
struct list_head v4llist; /* entry of the list of V4L cameras */
struct usb_device* usbdev; /* -> main USB structure */
struct urb* urb[W9968CF_URBS]; /* -> USB request block structs */
void* transfer_buffer[W9968CF_URBS]; /* -> ISO transfer buffers */
u16* control_buffer; /* -> buffer for control req.*/
u16* data_buffer; /* -> data to send to the FSB */
struct w9968cf_frame_t frame[W9968CF_MAX_BUFFERS];
struct w9968cf_frame_t frame_tmp; /* temporary frame */
struct w9968cf_frame_t* frame_current; /* -> frame being grabbed */
struct w9968cf_frame_t* requested_frame[W9968CF_MAX_BUFFERS];
void* vpp_buffer; /* -> helper buffer for post-processing routines */
u8 max_buffers, /* number of requested buffers */
force_palette, /* yes=1/no=0 */
force_rgb, /* read RGB instead of BGR, yes=1, no=0 */
double_buffer, /* hardware double buffering yes=1/no=0 */
clamping, /* video data clamping yes=1/no=0 */
filter_type, /* 0=disabled, 1=3 tap, 2=5 tap filter */
capture, /* 0=disabled, 1=enabled */
largeview, /* 0=disabled, 1=enabled */
decompression, /* 0=disabled, 1=forced, 2=allowed */
upscaling; /* software image scaling, 0=enabled, 1=disabled */
struct video_picture picture; /* current window settings */
struct video_window window; /* current picture settings */
u16 hw_depth, /* depth (used by the chip) */
hw_palette, /* palette (used by the chip) */
hw_width, /* width (used by the chip) */
hw_height, /* height (used by the chip) */
hs_polarity, /* 0=negative sync pulse, 1=positive sync pulse */
vs_polarity; /* 0=negative sync pulse, 1=positive sync pulse */
enum w9968cf_vpp_flag vpp_flag; /* post-processing routines in use */
u8 nbuffers, /* number of allocated frame buffers */
altsetting, /* camera alternate setting */
disconnected, /* flag: yes=1, no=0 */
misconfigured, /* flag: yes=1, no=0 */
users, /* flag: number of users holding the device */
streaming; /* flag: yes=1, no=0 */
int sensor; /* type of image CMOS sensor chip (CC_*) */
/* Determined by CMOS sensor type */
u16 maxwidth,
maxheight,
minwidth,
minheight,
start_cropx,
start_cropy;
u8 auto_brt, /* auto brightness enabled flag */
auto_exp, /* auto exposure enabled flag */
backlight, /* backlight exposure algorithm flag */
mirror, /* image is reversed horizontally */
lightfreq, /* power (lighting) frequency */
bandfilt; /* banding filter enabled flag */
s8 clockdiv; /* clock divisor */
int sensor_mono; /* CMOS sensor is (probably) monochrome */
/* I2C interface to kernel */
struct i2c_adapter i2c_adapter;
struct i2c_client* sensor_client;
/* Locks */
struct semaphore dev_sem, /* for probe, disconnect,open and close */
fileop_sem; /* for read and ioctl */
spinlock_t urb_lock, /* for submit_urb() and unlink_urb() */
flist_lock; /* for requested frame list accesses */
char command[16]; /* name of the program holding the device */
wait_queue_head_t open, wait_queue;
};
#define W9968CF_HW_BUF_SIZE 640*480*2 /* buf. size for original video frames */
#define SENSOR_FORMAT VIDEO_PALETTE_UYVY
#define SENSOR_FATAL_ERROR(rc) ((rc) < 0 && (rc) != -EPERM)
/****************************************************************************
* Macros and other constants *
****************************************************************************/
#undef DBG
#ifdef W9968CF_DEBUG
# define DBG(level, fmt, args...) \
{ \
if ( ((specific_debug) && (debug == (level))) || \
((!specific_debug) && (debug >= (level))) ) { \
if ((level) == 1) \
err(fmt, ## args); \
else if ((level) == 2 || (level) == 3) \
info(fmt, ## args); \
else if ((level) == 4) \
warn(fmt, ## args); \
else if ((level) >= 5) \
info("[%s,%d] " fmt, \
__PRETTY_FUNCTION__, __LINE__ , ## args); \
} \
}
#else
/* Not debugging: nothing */
# define DBG(level, fmt, args...) do {;} while(0);
#endif
#undef PDBG
#undef PDBGG
#define PDBG(fmt, args...) info("[%s, %d] "fmt, \
__PRETTY_FUNCTION__, __LINE__ , ## args);
#define PDBGG(fmt, args...) do {;} while(0); /* nothing: it's a placeholder */
#endif /* _W9968CF_H_ */
/***************************************************************************
* Video decoder for the W996[87]CF driver for Linux. *
* *
* Copyright (C) 2003 by Luca Risolia <luca_ing@libero.it> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
***************************************************************************/
#ifndef _W9968CF_DECODER_H_
#define _W9968CF_DECODER_H_
/* Comment/uncomment this for high/low quality of compressed video */
#define W9968CF_DEC_FAST_LOWQUALITY_VIDEO
#ifdef W9968CF_DEC_FAST_LOWQUALITY_VIDEO
static const unsigned char Y_QUANTABLE[64] = {
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99
};
static const unsigned char UV_QUANTABLE[64] = {
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
};
#else
static const unsigned char Y_QUANTABLE[64] = {
8, 5, 5, 8, 12, 20, 25, 30,
6, 6, 7, 9, 13, 29, 30, 27,
7, 6, 8, 12, 20, 28, 34, 28,
7, 8, 11, 14, 25, 43, 40, 31,
9, 11, 18, 28, 34, 54, 51, 38,
12, 17, 27, 32, 40, 52, 56, 46,
24, 32, 39, 43, 51, 60, 60, 50,
36, 46, 47, 49, 56, 50, 51, 49
};
static const unsigned char UV_QUANTABLE[64] = {
8, 9, 12, 23, 49, 49, 49, 49,
9, 10, 13, 33, 49, 49, 49, 49,
12, 13, 28, 49, 49, 49, 49, 49,
23, 33, 49, 49, 49, 49, 49, 49,
49, 49, 49, 49, 49, 49, 49, 49,
49, 49, 49, 49, 49, 49, 49, 49,
49, 49, 49, 49, 49, 49, 49, 49,
49, 49, 49, 49, 49, 49, 49, 49
};
#endif
#define W9968CF_DEC_ERR_CORRUPTED_DATA -1
#define W9968CF_DEC_ERR_BUF_OVERFLOW -2
#define W9968CF_DEC_ERR_NO_SOI -3
#define W9968CF_DEC_ERR_NO_SOF0 -4
#define W9968CF_DEC_ERR_NO_SOS -5
#define W9968CF_DEC_ERR_NO_EOI -6
extern void w9968cf_init_decoder(void);
extern int w9968cf_check_headers(const unsigned char* Pin,
const unsigned long BUF_SIZE);
extern int w9968cf_decode(const char* Pin, const unsigned long BUF_SIZE,
const unsigned W, const unsigned H, char* Pout);
#endif /* _W9968CF_DECODER_H_ */
/***************************************************************************
* Various definitions for compatibility with external modules. *
* This file is part of the W996[87]CF driver for Linux. *
* *
* Copyright (C) 2002 2003 by Luca Risolia <luca_ing@libero.it> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
***************************************************************************/
#ifndef _W9968CF_EXTERNALDEF_H_
#define _W9968CF_EXTERNALDEF_H_
#include <linux/videodev.h>
#include <linux/i2c.h>
#include <asm/ioctl.h>
#include <asm/types.h>
/* The following values have been copied from the "ovcamchip" module. */
#ifndef I2C_DRIVERID_OVCAMCHIP
# define I2C_DRIVERID_OVCAMCHIP 0xf00f
#endif
/* Controls */
enum {
OVCAMCHIP_CID_CONT, /* Contrast */
OVCAMCHIP_CID_BRIGHT, /* Brightness */
OVCAMCHIP_CID_SAT, /* Saturation */
OVCAMCHIP_CID_HUE, /* Hue */
OVCAMCHIP_CID_EXP, /* Exposure */
OVCAMCHIP_CID_FREQ, /* Light frequency */
OVCAMCHIP_CID_BANDFILT, /* Banding filter */
OVCAMCHIP_CID_AUTOBRIGHT, /* Auto brightness */
OVCAMCHIP_CID_AUTOEXP, /* Auto exposure */
OVCAMCHIP_CID_BACKLIGHT, /* Back light compensation */
OVCAMCHIP_CID_MIRROR, /* Mirror horizontally */
};
/* I2C addresses */
#define OV7xx0_SID (0x42 >> 1)
#define OV6xx0_SID (0xC0 >> 1)
/* Sensor types */
enum {
CC_UNKNOWN,
CC_OV76BE,
CC_OV7610,
CC_OV7620,
CC_OV7620AE,
CC_OV6620,
CC_OV6630,
CC_OV6630AE,
CC_OV6630AF,
};
/* API */
struct ovcamchip_control {
__u32 id;
__s32 value;
};
struct ovcamchip_window {
int x;
int y;
int width;
int height;
int format;
int quarter; /* Scale width and height down 2x */
/* This stuff will be removed eventually */
int clockdiv; /* Clock divisor setting */
};
/* Commands.
You must call OVCAMCHIP_CMD_INITIALIZE before any of other commands */
#define OVCAMCHIP_CMD_Q_SUBTYPE _IOR (0x88, 0x00, int)
#define OVCAMCHIP_CMD_INITIALIZE _IOW (0x88, 0x01, int)
#define OVCAMCHIP_CMD_S_CTRL _IOW (0x88, 0x02, struct ovcamchip_control)
#define OVCAMCHIP_CMD_G_CTRL _IOWR (0x88, 0x03, struct ovcamchip_control)
#define OVCAMCHIP_CMD_S_MODE _IOW (0x88, 0x04, struct ovcamchip_window)
#define OVCAMCHIP_MAX_CMD _IO (0x88, 0x3f)
#endif /* _W9968CF_EXTERNALDEF_H_ */
......@@ -58,6 +58,18 @@ config USB_RIO500
To compile this driver as a module, choose M here: the
module will be called rio500.
config USB_LEGOTOWER
tristate "USB Lego Infrared Tower support (EXPERIMENTAL)"
depends on USB && EXPERIMENTAL
help
Say Y here if you want to connect a USB Lego Infrared Tower to your
computer's USB port.
This code is also available as a module ( = code which can be
inserted in and removed from the running kernel whenever you want).
The module will be called legousbtower. If you want to compile it as
a module, say M here and read <file:Documentation/modules.txt>.
config USB_BRLVGER
tristate "Tieman Voyager USB Braille display support (EXPERIMENTAL)"
depends on USB && EXPERIMENTAL
......
......@@ -12,3 +12,4 @@ obj-$(CONFIG_USB_SPEEDTOUCH) += speedtch.o
obj-$(CONFIG_USB_TEST) += usbtest.o
obj-$(CONFIG_USB_TIGL) += tiglusb.o
obj-$(CONFIG_USB_USS720) += uss720.o
obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o
/*
* LEGO USB Tower driver
*
* Copyright (C) 2003 David Glance <davidgsf@sourceforge.net>
* 2001 Juergen Stuber <stuber@loria.fr>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* derived from USB Skeleton driver - 0.5
* Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com)
*
* History:
*
* 2001-10-13 - 0.1 js
* - first version
* 2001-11-03 - 0.2 js
* - simplified buffering, one-shot URBs for writing
* 2001-11-10 - 0.3 js
* - removed IOCTL (setting power/mode is more complicated, postponed)
* 2001-11-28 - 0.4 js
* - added vendor commands for mode of operation and power level in open
* 2001-12-04 - 0.5 js
* - set IR mode by default (by oversight 0.4 set VLL mode)
* 2002-01-11 - 0.5? pcchan
* - make read buffer reusable and work around bytes_to_write issue between
* uhci and legusbtower
* 2002-09-23 - 0.52 david (david@csse.uwa.edu.au)
* - imported into lejos project
* - changed wake_up to wake_up_interruptible
* - changed to use lego0 rather than tower0
* - changed dbg() to use __func__ rather than deprecated __FUNCTION__
* 2003-01-12 - 0.53 david (david@csse.uwa.edu.au)
* - changed read and write to write everything or timeout (from a patch by Chris Riesen and
* Brett Thaeler driver)
* - added ioctl functionality to set timeouts
* 2003-07-18 - 0.54 davidgsf (david@csse.uwa.edu.au)
* - initial import into LegoUSB project
* - merge of existing LegoUSB.c driver
* 2003-07-18 - 0.56 davidgsf (david@csse.uwa.edu.au)
* - port to 2.6 style driver
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/smp_lock.h>
#include <linux/completion.h>
#include <asm/uaccess.h>
#include <linux/usb.h>
#ifdef CONFIG_USB_DEBUG
static int debug = 4;
#else
static int debug = 1;
#endif
/* Use our own dbg macro */
#undef dbg
#define dbg(lvl, format, arg...) do { if (debug >= lvl) printk(KERN_DEBUG __FILE__ " : " format " \n", ## arg); } while (0)
/* Version Information */
#define DRIVER_VERSION "v0.56"
#define DRIVER_AUTHOR "David Glance, davidgsf@sourceforge.net"
#define DRIVER_DESC "LEGO USB Tower Driver"
/* Module paramaters */
MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug enabled or not");
/* Define these values to match your device */
#define LEGO_USB_TOWER_VENDOR_ID 0x0694
#define LEGO_USB_TOWER_PRODUCT_ID 0x0001
/* table of devices that work with this driver */
static struct usb_device_id tower_table [] = {
{ USB_DEVICE(LEGO_USB_TOWER_VENDOR_ID, LEGO_USB_TOWER_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, tower_table);
#define LEGO_USB_TOWER_MINOR_BASE 160
/* we can have up to this number of device plugged in at once */
#define MAX_DEVICES 16
#define COMMAND_TIMEOUT (2*HZ) /* 2 second timeout for a command */
/* Structure to hold all of our device specific stuff */
struct lego_usb_tower {
struct semaphore sem; /* locks this structure */
struct usb_device* udev; /* save off the usb device pointer */
struct usb_interface* interface;
unsigned char minor; /* the starting minor number for this device */
int open_count; /* number of times this port has been opened */
char* read_buffer;
int read_buffer_length;
wait_queue_head_t read_wait;
wait_queue_head_t write_wait;
char* interrupt_in_buffer;
struct usb_endpoint_descriptor* interrupt_in_endpoint;
struct urb* interrupt_in_urb;
char* interrupt_out_buffer;
struct usb_endpoint_descriptor* interrupt_out_endpoint;
struct urb* interrupt_out_urb;
};
/* Note that no locking is needed:
* read_buffer is arbitrated by read_buffer_length == 0
* interrupt_out_buffer is arbitrated by interrupt_out_urb->status == -EINPROGRESS
* interrupt_in_buffer belongs to urb alone and is overwritten on overflow
*/
/* local function prototypes */
static ssize_t tower_read (struct file *file, char *buffer, size_t count, loff_t *ppos);
static ssize_t tower_write (struct file *file, const char *buffer, size_t count, loff_t *ppos);
static inline void tower_delete (struct lego_usb_tower *dev);
static int tower_open (struct inode *inode, struct file *file);
static int tower_release (struct inode *inode, struct file *file);
static int tower_release_internal (struct lego_usb_tower *dev);
static void tower_abort_transfers (struct lego_usb_tower *dev);
static void tower_interrupt_in_callback (struct urb *urb, struct pt_regs *regs);
static void tower_interrupt_out_callback (struct urb *urb, struct pt_regs *regs);
static int tower_probe (struct usb_interface *interface, const struct usb_device_id *id);
static void tower_disconnect (struct usb_interface *interface);
/* prevent races between open() and disconnect */
static DECLARE_MUTEX (disconnect_sem);
/* file operations needed when we register this driver */
static struct file_operations tower_fops = {
.owner = THIS_MODULE,
.read = tower_read,
.write = tower_write,
.open = tower_open,
.release = tower_release,
};
/*
* usb class driver info in order to get a minor number from the usb core,
* and to have the device registered with devfs and the driver core
*/
static struct usb_class_driver tower_class = {
.name = "usb/legousbtower%d",
.fops = &tower_fops,
.mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH,
.minor_base = LEGO_USB_TOWER_MINOR_BASE,
};
/* usb specific object needed to register this driver with the usb subsystem */
static struct usb_driver tower_driver = {
.owner = THIS_MODULE,
.name = "legousbtower",
.probe = tower_probe,
.disconnect = tower_disconnect,
.id_table = tower_table,
};
/**
* lego_usb_tower_debug_data
*/
static inline void lego_usb_tower_debug_data (int level, const char *function, int size, const unsigned char *data)
{
int i;
if (debug < level)
return;
printk (KERN_DEBUG __FILE__": %s - length = %d, data = ", function, size);
for (i = 0; i < size; ++i) {
printk ("%.2x ", data[i]);
}
printk ("\n");
}
/**
* tower_delete
*/
static inline void tower_delete (struct lego_usb_tower *dev)
{
dbg(2, "%s enter", __func__);
tower_abort_transfers (dev);
/* free data structures */
if (dev->interrupt_in_urb != NULL) {
usb_free_urb (dev->interrupt_in_urb);
}
if (dev->interrupt_out_urb != NULL) {
usb_free_urb (dev->interrupt_out_urb);
}
kfree (dev->read_buffer);
kfree (dev->interrupt_in_buffer);
kfree (dev->interrupt_out_buffer);
kfree (dev);
dbg(2, "%s : leave", __func__);
}
/**
* tower_open
*/
static int tower_open (struct inode *inode, struct file *file)
{
struct lego_usb_tower *dev = NULL;
int subminor;
int retval = 0;
struct usb_interface *interface;
dbg(2,"%s : enter", __func__);
subminor = iminor(inode);
down (&disconnect_sem);
interface = usb_find_interface (&tower_driver, subminor);
if (!interface) {
err ("%s - error, can't find device for minor %d",
__FUNCTION__, subminor);
retval = -ENODEV;
goto exit_no_device;
}
dev = usb_get_intfdata(interface);
if (!dev) {
retval = -ENODEV;
goto exit_no_device;
}
/* lock this device */
down (&dev->sem);
/* increment our usage count for the device */
++dev->open_count;
/* save device in the file's private structure */
file->private_data = dev;
/* initialize in direction */
dev->read_buffer_length = 0;
up (&dev->sem);
exit_no_device:
up (&disconnect_sem);
dbg(2,"%s : leave, return value %d ", __func__, retval);
return retval;
}
/**
* tower_release
*/
static int tower_release (struct inode *inode, struct file *file)
{
struct lego_usb_tower *dev;
int retval = 0;
dbg(2," %s : enter", __func__);
dev = (struct lego_usb_tower *)file->private_data;
if (dev == NULL) {
dbg(1," %s : object is NULL", __func__);
retval = -ENODEV;
goto exit;
}
/* lock our device */
down (&dev->sem);
if (dev->open_count <= 0) {
dbg(1," %s : device not opened", __func__);
retval = -ENODEV;
goto exit;
}
/* do the work */
retval = tower_release_internal (dev);
exit:
up (&dev->sem);
dbg(2," %s : leave, return value %d", __func__, retval);
return retval;
}
/**
* tower_release_internal
*/
static int tower_release_internal (struct lego_usb_tower *dev)
{
int retval = 0;
dbg(2," %s : enter", __func__);
if (dev->udev == NULL) {
/* the device was unplugged before the file was released */
tower_delete (dev);
goto exit;
}
/* decrement our usage count for the device */
--dev->open_count;
if (dev->open_count <= 0) {
tower_abort_transfers (dev);
dev->open_count = 0;
}
exit:
dbg(2," %s : leave", __func__);
return retval;
}
/**
* tower_abort_transfers
* aborts transfers and frees associated data structures
*/
static void tower_abort_transfers (struct lego_usb_tower *dev)
{
dbg(2," %s : enter", __func__);
if (dev == NULL) {
dbg(1," %s : dev is null", __func__);
goto exit;
}
/* shutdown transfer */
if (dev->interrupt_in_urb != NULL) {
usb_unlink_urb (dev->interrupt_in_urb);
}
if (dev->interrupt_out_urb != NULL) {
usb_unlink_urb (dev->interrupt_out_urb);
}
exit:
dbg(2," %s : leave", __func__);
}
/**
* tower_read
*/
static ssize_t tower_read (struct file *file, char *buffer, size_t count, loff_t *ppos)
{
struct lego_usb_tower *dev;
size_t bytes_read = 0;
size_t bytes_to_read;
int i;
int retval = 0;
int timeout = 0;
dbg(2," %s : enter, count = %Zd", __func__, count);
dev = (struct lego_usb_tower *)file->private_data;
/* lock this object */
down (&dev->sem);
/* verify that the device wasn't unplugged */
if (dev->udev == NULL) {
retval = -ENODEV;
err("No device or device unplugged %d", retval);
goto exit;
}
/* verify that we actually have some data to read */
if (count == 0) {
dbg(1," %s : read request of 0 bytes", __func__);
goto exit;
}
timeout = COMMAND_TIMEOUT;
while (1) {
if (dev->read_buffer_length == 0) {
/* start reading */
usb_fill_int_urb (dev->interrupt_in_urb,dev->udev,
usb_rcvintpipe(dev->udev, dev->interrupt_in_endpoint->bEndpointAddress),
dev->interrupt_in_buffer,
dev->interrupt_in_endpoint->wMaxPacketSize,
tower_interrupt_in_callback,
dev,
dev->interrupt_in_endpoint->bInterval);
retval = usb_submit_urb (dev->interrupt_in_urb, GFP_KERNEL);
if (retval < 0) {
err("Couldn't submit interrupt_in_urb");
goto exit;
}
if (timeout <= 0) {
retval = -ETIMEDOUT;
goto exit;
}
if (signal_pending(current)) {
retval = -EINTR;
goto exit;
}
up (&dev->sem);
timeout = interruptible_sleep_on_timeout (&dev->read_wait, timeout);
down (&dev->sem);
} else {
/* copy the data from read_buffer into userspace */
bytes_to_read = count > dev->read_buffer_length ? dev->read_buffer_length : count;
if (copy_to_user (buffer, dev->read_buffer, bytes_to_read) != 0) {
retval = -EFAULT;
goto exit;
}
dev->read_buffer_length -= bytes_to_read;
for (i=0; i<dev->read_buffer_length; i++) {
dev->read_buffer[i] = dev->read_buffer[i+bytes_to_read];
}
buffer += bytes_to_read;
count -= bytes_to_read;
bytes_read += bytes_to_read;
if (count == 0) {
break;
}
}
}
retval = bytes_read;
exit:
/* unlock the device */
up (&dev->sem);
dbg(2," %s : leave, return value %d", __func__, retval);
return retval;
}
/**
* tower_write
*/
static ssize_t tower_write (struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
struct lego_usb_tower *dev;
size_t bytes_written = 0;
size_t bytes_to_write;
size_t buffer_size;
int retval = 0;
int timeout = 0;
dbg(2," %s : enter, count = %Zd", __func__, count);
dev = (struct lego_usb_tower *)file->private_data;
/* lock this object */
down (&dev->sem);
/* verify that the device wasn't unplugged */
if (dev->udev == NULL) {
retval = -ENODEV;
err("No device or device unplugged %d", retval);
goto exit;
}
/* verify that we actually have some data to write */
if (count == 0) {
dbg(1," %s : write request of 0 bytes", __func__);
goto exit;
}
while (count > 0) {
if (dev->interrupt_out_urb->status == -EINPROGRESS) {
timeout = COMMAND_TIMEOUT;
while (timeout > 0) {
if (signal_pending(current)) {
dbg(1," %s : interrupted", __func__);
retval = -EINTR;
goto exit;
}
up (&dev->sem);
timeout = interruptible_sleep_on_timeout (&dev->write_wait, timeout);
down (&dev->sem);
if (timeout > 0) {
break;
}
dbg(1," %s : interrupted timeout: %d", __func__, timeout);
}
dbg(1," %s : final timeout: %d", __func__, timeout);
if (timeout == 0) {
dbg(1, "%s - command timed out.", __func__);
retval = -ETIMEDOUT;
goto exit;
}
dbg(4," %s : in progress, count = %Zd", __func__, count);
} else {
dbg(4," %s : sending, count = %Zd", __func__, count);
/* write the data into interrupt_out_buffer from userspace */
buffer_size = dev->interrupt_out_endpoint->wMaxPacketSize;
bytes_to_write = count > buffer_size ? buffer_size : count;
dbg(4," %s : buffer_size = %Zd, count = %Zd, bytes_to_write = %Zd", __func__, buffer_size, count, bytes_to_write);
if (copy_from_user (dev->interrupt_out_buffer, buffer, bytes_to_write) != 0) {
retval = -EFAULT;
goto exit;
}
/* send off the urb */
usb_fill_int_urb(dev->interrupt_out_urb,
dev->udev,
usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress),
dev->interrupt_out_buffer,
bytes_to_write,
tower_interrupt_out_callback,
dev,
dev->interrupt_in_endpoint->bInterval);
dev->interrupt_out_urb->actual_length = bytes_to_write;
retval = usb_submit_urb (dev->interrupt_out_urb, GFP_KERNEL);
if (retval < 0) {
err("Couldn't submit interrupt_out_urb %d", retval);
goto exit;
}
buffer += bytes_to_write;
count -= bytes_to_write;
bytes_written += bytes_to_write;
}
}
retval = bytes_written;
exit:
/* unlock the device */
up (&dev->sem);
dbg(2," %s : leave, return value %d", __func__, retval);
return retval;
}
/**
* tower_interrupt_in_callback
*/
static void tower_interrupt_in_callback (struct urb *urb, struct pt_regs *regs)
{
struct lego_usb_tower *dev = (struct lego_usb_tower *)urb->context;
dbg(4," %s : enter, status %d", __func__, urb->status);
lego_usb_tower_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer);
if (urb->status != 0) {
if ((urb->status != -ENOENT) && (urb->status != -ECONNRESET)) {
dbg(1," %s : nonzero status received: %d", __func__, urb->status);
}
goto exit;
}
down (&dev->sem);
if (urb->actual_length > 0) {
if (dev->read_buffer_length < (4 * dev->interrupt_in_endpoint->wMaxPacketSize) - (urb->actual_length)) {
memcpy (dev->read_buffer+dev->read_buffer_length, dev->interrupt_in_buffer, urb->actual_length);
dev->read_buffer_length += urb->actual_length;
dbg(1," %s reading %d ", __func__, urb->actual_length);
wake_up_interruptible (&dev->read_wait);
} else {
dbg(1," %s : read_buffer overflow", __func__);
}
}
up (&dev->sem);
exit:
lego_usb_tower_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer);
dbg(4," %s : leave, status %d", __func__, urb->status);
}
/**
* tower_interrupt_out_callback
*/
static void tower_interrupt_out_callback (struct urb *urb, struct pt_regs *regs)
{
struct lego_usb_tower *dev = (struct lego_usb_tower *)urb->context;
dbg(4," %s : enter, status %d", __func__, urb->status);
lego_usb_tower_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer);
if (urb->status != 0) {
if ((urb->status != -ENOENT) &&
(urb->status != -ECONNRESET)) {
dbg(1, " %s :nonzero status received: %d", __func__, urb->status);
}
goto exit;
}
wake_up_interruptible(&dev->write_wait);
exit:
lego_usb_tower_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer);
dbg(4," %s : leave, status %d", __func__, urb->status);
}
/**
* tower_probe
*
* Called by the usb core when a new device is connected that it thinks
* this driver might be interested in.
*/
static int tower_probe (struct usb_interface *interface, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(interface);
struct lego_usb_tower *dev = NULL;
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor* endpoint;
int i;
int retval = -ENOMEM;
dbg(2," %s : enter", __func__);
if (udev == NULL) {
info ("udev is NULL.");
}
/* See if the device offered us matches what we can accept */
if ((udev->descriptor.idVendor != LEGO_USB_TOWER_VENDOR_ID) ||
(udev->descriptor.idProduct != LEGO_USB_TOWER_PRODUCT_ID)) {
return -ENODEV;
}
/* allocate memory for our device state and intialize it */
dev = kmalloc (sizeof(struct lego_usb_tower), GFP_KERNEL);
if (dev == NULL) {
err ("Out of memory");
goto exit;
}
init_MUTEX (&dev->sem);
dev->udev = udev;
dev->open_count = 0;
dev->read_buffer = NULL;
dev->read_buffer_length = 0;
init_waitqueue_head (&dev->read_wait);
init_waitqueue_head (&dev->write_wait);
dev->interrupt_in_buffer = NULL;
dev->interrupt_in_endpoint = NULL;
dev->interrupt_in_urb = NULL;
dev->interrupt_out_buffer = NULL;
dev->interrupt_out_endpoint = NULL;
dev->interrupt_out_urb = NULL;
iface_desc = &interface->altsetting[0];
/* set up the endpoint information */
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)) {
dev->interrupt_in_endpoint = endpoint;
}
if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)) {
dev->interrupt_out_endpoint = endpoint;
}
}
if(dev->interrupt_in_endpoint == NULL) {
err("interrupt in endpoint not found");
goto error;
}
if (dev->interrupt_out_endpoint == NULL) {
err("interrupt out endpoint not found");
goto error;
}
dev->read_buffer = kmalloc ((4*dev->interrupt_in_endpoint->wMaxPacketSize), GFP_KERNEL);
if (!dev->read_buffer) {
err("Couldn't allocate read_buffer");
goto error;
}
dev->interrupt_in_buffer = kmalloc (dev->interrupt_in_endpoint->wMaxPacketSize, GFP_KERNEL);
if (!dev->interrupt_in_buffer) {
err("Couldn't allocate interrupt_in_buffer");
goto error;
}
dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!dev->interrupt_in_urb) {
err("Couldn't allocate interrupt_in_urb");
goto error;
}
dev->interrupt_out_buffer = kmalloc (dev->interrupt_out_endpoint->wMaxPacketSize, GFP_KERNEL);
if (!dev->interrupt_out_buffer) {
err("Couldn't allocate interrupt_out_buffer");
goto error;
}
dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!dev->interrupt_out_urb) {
err("Couldn't allocate interrupt_out_urb");
goto error;
}
/* we can register the device now, as it is ready */
usb_set_intfdata (interface, dev);
retval = usb_register_dev (interface, &tower_class);
if (retval) {
/* something prevented us from registering this driver */
err ("Not able to get a minor for this device.");
usb_set_intfdata (interface, NULL);
goto error;
}
dev->minor = interface->minor;
/* let the user know what node this device is now attached to */
info ("LEGO USB Tower device now attached to /dev/usb/lego%d", (dev->minor - LEGO_USB_TOWER_MINOR_BASE));
exit:
dbg(2," %s : leave, return value 0x%.8lx (dev)", __func__, (long) dev);
return retval;
error:
tower_delete(dev);
return retval;
}
/**
* tower_disconnect
*
* Called by the usb core when the device is removed from the system.
*/
static void tower_disconnect (struct usb_interface *interface)
{
struct lego_usb_tower *dev;
int minor;
dbg(2," %s : enter", __func__);
down (&disconnect_sem);
dev = usb_get_intfdata (interface);
usb_set_intfdata (interface, NULL);
down (&dev->sem);
minor = dev->minor;
/* give back our minor */
usb_deregister_dev (interface, &tower_class);
/* if the device is not opened, then we clean up right now */
if (!dev->open_count) {
up (&dev->sem);
tower_delete (dev);
} else {
dev->udev = NULL;
up (&dev->sem);
}
up (&disconnect_sem);
info("LEGO USB Tower #%d now disconnected", (minor - LEGO_USB_TOWER_MINOR_BASE));
dbg(2," %s : leave", __func__);
}
/**
* lego_usb_tower_init
*/
static int __init lego_usb_tower_init(void)
{
int result;
int retval = 0;
dbg(2," %s : enter", __func__);
/* register this driver with the USB subsystem */
result = usb_register(&tower_driver);
if (result < 0) {
err("usb_register failed for the "__FILE__" driver. Error number %d", result);
retval = -1;
goto exit;
}
info(DRIVER_DESC " " DRIVER_VERSION);
exit:
dbg(2," %s : leave, return value %d", __func__, retval);
return retval;
}
/**
* lego_usb_tower_exit
*/
static void __exit lego_usb_tower_exit(void)
{
dbg(2," %s : enter", __func__);
/* deregister this driver with the USB subsystem */
usb_deregister (&tower_driver);
dbg(2," %s : leave", __func__);
}
module_init (lego_usb_tower_init);
module_exit (lego_usb_tower_exit);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
......@@ -637,6 +637,27 @@ static void ax8817x_get_drvinfo (struct net_device *net,
info->eedump_len = 0x3e;
}
static u32 ax8817x_get_link (struct net_device *net)
{
struct usbnet *dev = (struct usbnet *)net->priv;
return (u32)mii_link_ok(&dev->mii);
}
static int ax8817x_get_settings(struct net_device *net, struct ethtool_cmd *cmd)
{
struct usbnet *dev = (struct usbnet *)net->priv;
return mii_ethtool_gset(&dev->mii,cmd);
}
static int ax8817x_set_settings(struct net_device *net, struct ethtool_cmd *cmd)
{
struct usbnet *dev = (struct usbnet *)net->priv;
return mii_ethtool_sset(&dev->mii,cmd);
}
static int ax8817x_bind(struct usbnet *dev, struct usb_interface *intf)
{
int ret;
......@@ -670,16 +691,6 @@ static int ax8817x_bind(struct usbnet *dev, struct usb_interface *intf)
}
memcpy(dev->net->dev_addr, buf, ETH_ALEN);
/* Get IPG values */
if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_IPG012, 0, 0, 3, buf)) < 0) {
dbg("Error reading IPG values: %d", ret);
return ret;
}
for(i = 0;i < 3;i++) {
ax8817x_write_cmd(dev, AX_CMD_WRITE_IPG0 + i, 0, 0, 1, &buf[i]);
}
/* Get the PHY id */
if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf)) < 0) {
dbg("error on read AX_CMD_READ_PHY_ID: %02x", ret);
......@@ -735,9 +746,12 @@ static int ax8817x_bind(struct usbnet *dev, struct usb_interface *intf)
dev->net->set_multicast_list = ax8817x_set_multicast;
usbnet_ethtool_ops.get_drvinfo = &ax8817x_get_drvinfo;
usbnet_ethtool_ops.get_link = &ax8817x_get_link;
usbnet_ethtool_ops.get_wol = &ax8817x_get_wol;
usbnet_ethtool_ops.set_wol = &ax8817x_set_wol;
usbnet_ethtool_ops.get_eeprom = &ax8817x_get_eeprom;
usbnet_ethtool_ops.get_settings = &ax8817x_get_settings;
usbnet_ethtool_ops.set_settings = &ax8817x_set_settings;
return 0;
}
......
......@@ -342,6 +342,10 @@ static struct usb_device_id id_table_8U232AM [] = {
{ USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_8_PID, 0, 0x3ff) },
{ USB_DEVICE_VER(IDTECH_VID, IDTECH_IDT1221U_PID, 0, 0x3ff) },
{ USB_DEVICE_VER(OCT_VID, OCT_US101_PID, 0, 0x3ff) },
{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_1, 0, 0x3ff) },
{ USB_DEVICE_VER(FTDI_VID, PROTEGO_R2X0, 0, 0x3ff) },
{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_3, 0, 0x3ff) },
{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_4, 0, 0x3ff) },
{ } /* Terminating entry */
};
......@@ -416,6 +420,10 @@ static struct usb_device_id id_table_FT232BM [] = {
{ USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_8_PID, 0x400, 0xffff) },
{ USB_DEVICE_VER(IDTECH_VID, IDTECH_IDT1221U_PID, 0x400, 0xffff) },
{ USB_DEVICE_VER(OCT_VID, OCT_US101_PID, 0x400, 0xffff) },
{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_1, 0x400, 0xffff) },
{ USB_DEVICE_VER(FTDI_VID, PROTEGO_R2X0, 0x400, 0xffff) },
{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_3, 0x400, 0xffff) },
{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_4, 0x400, 0xffff) },
{ } /* Terminating entry */
};
......@@ -505,6 +513,10 @@ static struct usb_device_id id_table_combined [] = {
{ USB_DEVICE(OCT_VID, OCT_US101_PID) },
{ USB_DEVICE_VER(FTDI_VID, FTDI_HE_TIRA1_PID, 0x400, 0xffff) },
{ USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID) },
{ USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_1) },
{ USB_DEVICE(FTDI_VID, PROTEGO_R2X0) },
{ USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_3) },
{ USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_4) },
{ } /* Terminating entry */
};
......
......@@ -145,6 +145,14 @@
/* Note: OCT US101 is also rebadged as Dick Smith Electronics (NZ) XH6381 */
#define OCT_US101_PID 0x0421 /* OCT US101 USB to RS-232 */
/*
* Protego product ids
*/
#define PROTEGO_SPECIAL_1 0xFC70 /* special/unknown device */
#define PROTEGO_R2X0 0xFC71 /* R200-USB TRNG unit (R210, R220, and R230) */
#define PROTEGO_SPECIAL_3 0xFC72 /* special/unknown device */
#define PROTEGO_SPECIAL_4 0xFC73 /* special/unknown device */
/* Commands */
#define FTDI_SIO_RESET 0 /* Reset the port */
#define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */
......
......@@ -258,6 +258,7 @@
#define I2C_HW_SMBUS_AMD8111 0x0a
#define I2C_HW_SMBUS_SCX200 0x0b
#define I2C_HW_SMBUS_NFORCE2 0x0c
#define I2C_HW_SMBUS_W9968CF 0x0d
/* --- ISA pseudo-adapter */
#define I2C_HW_ISA 0x00
......
......@@ -429,6 +429,7 @@ struct video_code
#define VID_HARDWARE_CPIA2 33
#define VID_HARDWARE_VICAM 34
#define VID_HARDWARE_SF16FMR2 35
#define VID_HARDWARE_W9968CF 36 /* W996[87]CF JPEG USB Dual Mode Cam */
#endif /* __LINUX_VIDEODEV_H */
/*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment