Commit 795e6eb3 authored by Hans de Goede's avatar Hans de Goede Committed by Mauro Carvalho Chehab

[media] pwc: Remove software emulation of arbritary resolutions

The pwc driver claims to support any resolution between 160x120
and 640x480, but emulates this by simply drawing a black border
around the image. Userspace can draw its own black border if it
really wants one.
Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent a08d2c72
......@@ -460,16 +460,6 @@ Who: Jean Delvare <khali@linux-fr.org>
----------------------------
What: Software emulation of arbritary resolutions in the pwc driver
When: 3.3
Why: The pwc driver claims to support any resolution between 160x120
and 640x480, but emulates this by simply drawing a black border
around the image. Userspace can draw its own black border if it
really wants one.
Who: Hans de Goede <hdegoede@redhat.com>
----------------------------
What: For VIDIOC_S_FREQUENCY the type field must match the device node's type.
If not, return -EINVAL.
When: 3.2
......
......@@ -102,8 +102,6 @@ static struct Nala_table_entry Nala_table[PSZ_MAX][PWC_FPS_MAX_NALA] =
#include "pwc-nala.h"
};
static void pwc_set_image_buffer_size(struct pwc_device *pdev);
/****************************************************************************/
static int _send_control_msg(struct pwc_device *pdev,
......@@ -221,8 +219,9 @@ static int set_video_mode_Nala(struct pwc_device *pdev, int size, int frames)
/* Set various parameters */
pdev->vframes = frames;
pdev->valternate = pEntry->alternate;
pdev->image = pwc_image_sizes[size];
pdev->frame_size = (pdev->image.x * pdev->image.y * 3) / 2;
pdev->width = pwc_image_sizes[size][0];
pdev->height = pwc_image_sizes[size][1];
pdev->frame_size = (pdev->width * pdev->height * 3) / 2;
if (pEntry->compressed) {
if (pdev->release < 5) { /* 4 fold compression */
pdev->vbandlength = 528;
......@@ -282,12 +281,13 @@ static int set_video_mode_Timon(struct pwc_device *pdev, int size, int frames,
/* Set various parameters */
pdev->vframes = frames;
pdev->valternate = pChoose->alternate;
pdev->image = pwc_image_sizes[size];
pdev->width = pwc_image_sizes[size][0];
pdev->height = pwc_image_sizes[size][1];
pdev->vbandlength = pChoose->bandlength;
if (pChoose->bandlength > 0)
pdev->frame_size = (pChoose->bandlength * pdev->image.y) / 4;
pdev->frame_size = (pChoose->bandlength * pdev->height) / 4;
else
pdev->frame_size = (pdev->image.x * pdev->image.y * 12) / 8;
pdev->frame_size = (pdev->width * pdev->height * 12) / 8;
return 0;
}
......@@ -339,37 +339,25 @@ static int set_video_mode_Kiara(struct pwc_device *pdev, int size, int frames,
/* All set and go */
pdev->vframes = frames;
pdev->valternate = pChoose->alternate;
pdev->image = pwc_image_sizes[size];
pdev->width = pwc_image_sizes[size][0];
pdev->height = pwc_image_sizes[size][1];
pdev->vbandlength = pChoose->bandlength;
if (pdev->vbandlength > 0)
pdev->frame_size = (pdev->vbandlength * pdev->image.y) / 4;
pdev->frame_size = (pdev->vbandlength * pdev->height) / 4;
else
pdev->frame_size = (pdev->image.x * pdev->image.y * 12) / 8;
pdev->frame_size = (pdev->width * pdev->height * 12) / 8;
PWC_TRACE("frame_size=%d, vframes=%d, vsize=%d, vbandlength=%d\n",
pdev->frame_size, pdev->vframes, size, pdev->vbandlength);
return 0;
}
/**
@pdev: device structure
@width: viewport width
@height: viewport height
@frame: framerate, in fps
@compression: preferred compression ratio
*/
int pwc_set_video_mode(struct pwc_device *pdev, int width, int height,
int frames, int compression)
{
int ret, size;
PWC_DEBUG_FLOW("set_video_mode(%dx%d @ %d, pixfmt %08x).\n", width, height, frames, pdev->pixfmt);
size = pwc_decode_size(pdev, width, height);
if (size < 0) {
PWC_DEBUG_MODULE("Could not find suitable size.\n");
return -ERANGE;
}
size = pwc_get_size(pdev, width, height);
PWC_TRACE("decode_size = %d.\n", size);
if (DEVICE_USE_CODEC1(pdev->type)) {
......@@ -385,12 +373,9 @@ int pwc_set_video_mode(struct pwc_device *pdev, int width, int height,
PWC_ERROR("Failed to set video mode %s@%d fps; return code = %d\n", size2name[size], frames, ret);
return ret;
}
pdev->view.x = width;
pdev->view.y = height;
pdev->vcompression = compression;
pdev->frame_total_size = pdev->frame_size + pdev->frame_header_size + pdev->frame_trailer_size;
pwc_set_image_buffer_size(pdev);
PWC_DEBUG_SIZE("Set viewport to %dx%d, image size is %dx%d.\n", width, height, pwc_image_sizes[size].x, pwc_image_sizes[size].y);
PWC_DEBUG_SIZE("Set resolution to %dx%d\n", pdev->width, pdev->height);
return 0;
}
......@@ -447,34 +432,6 @@ unsigned int pwc_get_fps(struct pwc_device *pdev, unsigned int index, unsigned i
return ret;
}
static void pwc_set_image_buffer_size(struct pwc_device *pdev)
{
int factor = 0;
/* for V4L2_PIX_FMT_YUV420 */
switch (pdev->pixfmt) {
case V4L2_PIX_FMT_YUV420:
factor = 6;
break;
case V4L2_PIX_FMT_PWC1:
case V4L2_PIX_FMT_PWC2:
factor = 6; /* can be uncompressed YUV420P */
break;
}
/* Set sizes in bytes */
pdev->image.size = pdev->image.x * pdev->image.y * factor / 4;
pdev->view.size = pdev->view.x * pdev->view.y * factor / 4;
/* Align offset, or you'll get some very weird results in
YUV420 mode... x must be multiple of 4 (to get the Y's in
place), and y even (or you'll mixup U & V). This is less of a
problem for YUV420P.
*/
pdev->offset.x = ((pdev->view.x - pdev->image.x) / 2) & 0xFFFC;
pdev->offset.y = ((pdev->view.y - pdev->image.y) / 2) & 0xFFFE;
}
int pwc_get_u8_ctrl(struct pwc_device *pdev, u8 request, u16 value, int *data)
{
int ret;
......
......@@ -656,10 +656,6 @@ static void DecompressBand23(struct pwc_dec23_private *pdec,
*
* Uncompress a pwc23 buffer.
*
* pwc.view: size of the image wanted
* pwc.image: size of the image returned by the camera
* pwc.offset: (x,y) to displayer image in the view
*
* src: raw data
* dst: image output
*/
......@@ -667,7 +663,7 @@ void pwc_dec23_decompress(const struct pwc_device *pwc,
const void *src,
void *dst)
{
int bandlines_left, stride, bytes_per_block;
int bandlines_left, bytes_per_block;
struct pwc_dec23_private *pdec = pwc->decompress_data;
/* YUV420P image format */
......@@ -678,28 +674,23 @@ void pwc_dec23_decompress(const struct pwc_device *pwc,
mutex_lock(&pdec->lock);
bandlines_left = pwc->image.y / 4;
bytes_per_block = pwc->view.x * 4;
plane_size = pwc->view.x * pwc->view.y;
/* offset in Y plane */
stride = pwc->view.x * pwc->offset.y;
pout_planar_y = dst + stride + pwc->offset.x;
bandlines_left = pwc->height / 4;
bytes_per_block = pwc->width * 4;
plane_size = pwc->height * pwc->width;
/* offsets in U/V planes */
stride = (pwc->view.x * pwc->offset.y) / 4 + pwc->offset.x / 2;
pout_planar_u = dst + plane_size + stride;
pout_planar_v = dst + plane_size + plane_size / 4 + stride;
pout_planar_y = dst;
pout_planar_u = dst + plane_size;
pout_planar_v = dst + plane_size + plane_size / 4;
while (bandlines_left--) {
DecompressBand23(pwc->decompress_data,
src,
pout_planar_y, pout_planar_u, pout_planar_v,
pwc->image.x, pwc->view.x);
pwc->width, pwc->width);
src += pwc->vbandlength;
pout_planar_y += bytes_per_block;
pout_planar_u += pwc->view.x;
pout_planar_v += pwc->view.x;
pout_planar_u += pwc->width;
pout_planar_v += pwc->width;
}
mutex_unlock(&pdec->lock);
}
......@@ -656,6 +656,7 @@ static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
unsigned int sizes[], void *alloc_ctxs[])
{
struct pwc_device *pdev = vb2_get_drv_priv(vq);
int size;
if (*nbuffers < MIN_FRAMES)
*nbuffers = MIN_FRAMES;
......@@ -664,7 +665,9 @@ static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
*nplanes = 1;
sizes[0] = PAGE_ALIGN((pdev->abs_max.x * pdev->abs_max.y * 3) / 2);
size = pwc_get_size(pdev, MAX_WIDTH, MAX_HEIGHT);
sizes[0] = PAGE_ALIGN(pwc_image_sizes[size][0] *
pwc_image_sizes[size][1] * 3 / 2);
return 0;
}
......@@ -742,7 +745,7 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count)
pwc_camera_power(pdev, 1);
if (pdev->power_save) {
/* Restore video mode */
pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y,
pwc_set_video_mode(pdev, pdev->width, pdev->height,
pdev->vframes, pdev->vcompression);
}
pwc_set_leds(pdev, led_on, led_off);
......@@ -1056,7 +1059,6 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
}
pdev->type = type_id;
pdev->vframes = default_fps;
strcpy(pdev->serial, serial_number);
pdev->features = features;
pwc_construct(pdev); /* set min/max sizes correct */
......@@ -1119,7 +1121,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
pwc_set_leds(pdev, 0, 0);
/* Setup intial videomode */
rc = pwc_set_video_mode(pdev, pdev->view_max.x, pdev->view_max.y,
rc = pwc_set_video_mode(pdev, MAX_WIDTH, MAX_HEIGHT,
pdev->vframes, pdev->vcompression);
if (rc)
goto err_free_mem;
......
......@@ -27,67 +27,47 @@
#include "pwc.h"
const struct pwc_coord pwc_image_sizes[PSZ_MAX] =
const int pwc_image_sizes[PSZ_MAX][2] =
{
{ 128, 96, 0 }, /* sqcif */
{ 160, 120, 0 }, /* qsif */
{ 176, 144, 0 }, /* qcif */
{ 320, 240, 0 }, /* sif */
{ 352, 288, 0 }, /* cif */
{ 640, 480, 0 }, /* vga */
{ 128, 96 }, /* sqcif */
{ 160, 120 }, /* qsif */
{ 176, 144 }, /* qcif */
{ 320, 240 }, /* sif */
{ 352, 288 }, /* cif */
{ 640, 480 }, /* vga */
};
/* x,y -> PSZ_ */
int pwc_decode_size(struct pwc_device *pdev, int width, int height)
int pwc_get_size(struct pwc_device *pdev, int width, int height)
{
int i, find;
/* Make sure we don't go beyond our max size.
NB: we have different limits for RAW and normal modes. In case
you don't have the decompressor loaded or use RAW mode,
the maximum viewable size is smaller.
*/
if (pdev->pixfmt != V4L2_PIX_FMT_YUV420)
{
if (width > pdev->abs_max.x || height > pdev->abs_max.y)
{
PWC_DEBUG_SIZE("VIDEO_PALETTE_RAW: going beyond abs_max.\n");
return -1;
}
}
else
{
if (width > pdev->view_max.x || height > pdev->view_max.y)
{
PWC_DEBUG_SIZE("VIDEO_PALETTE_not RAW: going beyond view_max.\n");
return -1;
}
}
int i;
/* Find the largest size supported by the camera that fits into the
requested size.
*/
find = -1;
for (i = 0; i < PSZ_MAX; i++) {
if (pdev->image_mask & (1 << i)) {
if (pwc_image_sizes[i].x <= width && pwc_image_sizes[i].y <= height)
find = i;
requested size. */
for (i = PSZ_MAX - 1; i >= 0; i--) {
if (!(pdev->image_mask & (1 << i)))
continue;
if (pwc_image_sizes[i][0] <= width &&
pwc_image_sizes[i][1] <= height)
return i;
}
/* No mode found, return the smallest mode we have */
for (i = 0; i < PSZ_MAX; i++) {
if (pdev->image_mask & (1 << i))
return i;
}
return find;
/* Never reached there always is atleast one supported mode */
return 0;
}
/* initialize variables depending on type and decompressor*/
/* initialize variables depending on type and decompressor */
void pwc_construct(struct pwc_device *pdev)
{
if (DEVICE_USE_CODEC1(pdev->type)) {
pdev->view_min.x = 128;
pdev->view_min.y = 96;
pdev->view_max.x = 352;
pdev->view_max.y = 288;
pdev->abs_max.x = 352;
pdev->abs_max.y = 288;
pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QCIF | 1 << PSZ_CIF;
pdev->vcinterface = 2;
pdev->vendpoint = 4;
......@@ -96,13 +76,7 @@ void pwc_construct(struct pwc_device *pdev)
} else if (DEVICE_USE_CODEC3(pdev->type)) {
pdev->view_min.x = 160;
pdev->view_min.y = 120;
pdev->view_max.x = 640;
pdev->view_max.y = 480;
pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF | 1 << PSZ_VGA;
pdev->abs_max.x = 640;
pdev->abs_max.y = 480;
pdev->vcinterface = 3;
pdev->vendpoint = 5;
pdev->frame_header_size = TOUCAM_HEADER_SIZE;
......@@ -110,20 +84,11 @@ void pwc_construct(struct pwc_device *pdev)
} else /* if (DEVICE_USE_CODEC2(pdev->type)) */ {
pdev->view_min.x = 128;
pdev->view_min.y = 96;
/* Anthill bug #38: PWC always reports max size, even without PWCX */
pdev->view_max.x = 640;
pdev->view_max.y = 480;
pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF | 1 << PSZ_VGA;
pdev->abs_max.x = 640;
pdev->abs_max.y = 480;
pdev->vcinterface = 3;
pdev->vendpoint = 4;
pdev->frame_header_size = 0;
pdev->frame_trailer_size = 0;
}
pdev->pixfmt = V4L2_PIX_FMT_YUV420; /* default */
pdev->view_min.size = pdev->view_min.x * pdev->view_min.y;
pdev->view_max.size = pdev->view_max.x * pdev->view_max.y;
}
......@@ -35,7 +35,7 @@
int pwc_decompress(struct pwc_device *pdev, struct pwc_frame_buf *fbuf)
{
int n, line, col, stride;
int n, line, col;
void *yuv, *image;
u16 *src;
u16 *dsty, *dstu, *dstv;
......@@ -60,35 +60,23 @@ int pwc_decompress(struct pwc_device *pdev, struct pwc_frame_buf *fbuf)
return 0;
}
vb2_set_plane_payload(&fbuf->vb, 0, pdev->view.size);
vb2_set_plane_payload(&fbuf->vb, 0,
pdev->width * pdev->height * 3 / 2);
if (pdev->vbandlength == 0) {
/* Uncompressed mode.
* We copy the data into the output buffer, using the viewport
* size (which may be larger than the image size).
* Unfortunately we have to do a bit of byte stuffing to get
* the desired output format/size.
*
* We do some byte shuffling here to go from the
* native format to YUV420P.
*/
src = (u16 *)yuv;
n = pdev->view.x * pdev->view.y;
/* offset in Y plane */
stride = pdev->view.x * pdev->offset.y + pdev->offset.x;
dsty = (u16 *)(image + stride);
/* offsets in U/V planes */
stride = pdev->view.x * pdev->offset.y / 4 + pdev->offset.x / 2;
dstu = (u16 *)(image + n + stride);
dstv = (u16 *)(image + n + n / 4 + stride);
/* increment after each line */
stride = (pdev->view.x - pdev->image.x) / 2; /* u16 is 2 bytes */
n = pdev->width * pdev->height;
dsty = (u16 *)(image);
dstu = (u16 *)(image + n);
dstv = (u16 *)(image + n + n / 4);
for (line = 0; line < pdev->image.y; line++) {
for (col = 0; col < pdev->image.x; col += 4) {
for (line = 0; line < pdev->height; line++) {
for (col = 0; col < pdev->width; col += 4) {
*dsty++ = *src++;
*dsty++ = *src++;
if (line & 1)
......@@ -96,11 +84,6 @@ int pwc_decompress(struct pwc_device *pdev, struct pwc_frame_buf *fbuf)
else
*dstu++ = *src++;
}
dsty += stride;
if (line & 1)
dstv += (stride >> 1);
else
dstu += (stride >> 1);
}
return 0;
......@@ -122,6 +105,3 @@ int pwc_decompress(struct pwc_device *pdev, struct pwc_frame_buf *fbuf)
}
return 0;
}
/* vim: set cino= formatoptions=croql cindent shiftwidth=8 tabstop=8: */
......@@ -398,8 +398,8 @@ int pwc_init_controls(struct pwc_device *pdev)
static void pwc_vidioc_fill_fmt(const struct pwc_device *pdev, struct v4l2_format *f)
{
memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format));
f->fmt.pix.width = pdev->view.x;
f->fmt.pix.height = pdev->view.y;
f->fmt.pix.width = pdev->width;
f->fmt.pix.height = pdev->height;
f->fmt.pix.field = V4L2_FIELD_NONE;
if (pdev->pixfmt == V4L2_PIX_FMT_YUV420) {
f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
......@@ -429,6 +429,8 @@ static void pwc_vidioc_fill_fmt(const struct pwc_device *pdev, struct v4l2_forma
/* ioctl(VIDIOC_TRY_FMT) */
static int pwc_vidioc_try_fmt(struct pwc_device *pdev, struct v4l2_format *f)
{
int size;
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
PWC_DEBUG_IOCTL("Bad video type must be V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
return -EINVAL;
......@@ -455,15 +457,9 @@ static int pwc_vidioc_try_fmt(struct pwc_device *pdev, struct v4l2_format *f)
}
if (f->fmt.pix.width > pdev->view_max.x)
f->fmt.pix.width = pdev->view_max.x;
else if (f->fmt.pix.width < pdev->view_min.x)
f->fmt.pix.width = pdev->view_min.x;
if (f->fmt.pix.height > pdev->view_max.y)
f->fmt.pix.height = pdev->view_max.y;
else if (f->fmt.pix.height < pdev->view_min.y)
f->fmt.pix.height = pdev->view_min.y;
size = pwc_get_size(pdev, f->fmt.pix.width, f->fmt.pix.height);
f->fmt.pix.width = pwc_image_sizes[size][0];
f->fmt.pix.height = pwc_image_sizes[size][1];
return 0;
}
......@@ -972,7 +968,7 @@ static int pwc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
mutex_lock(&pdev->udevlock); /* To avoid race with s_fmt */
PWC_DEBUG_IOCTL("ioctl(VIDIOC_G_FMT) return size %dx%d\n",
pdev->image.x, pdev->image.y);
pdev->width, pdev->height);
pwc_vidioc_fill_fmt(pdev, f);
mutex_unlock(&pdev->udevlock);
return 0;
......@@ -1061,26 +1057,22 @@ static int pwc_enum_framesizes(struct file *file, void *fh,
struct pwc_device *pdev = video_drvdata(file);
unsigned int i = 0, index = fsize->index;
if (fsize->pixel_format == V4L2_PIX_FMT_YUV420) {
if (fsize->pixel_format == V4L2_PIX_FMT_YUV420 ||
(fsize->pixel_format == V4L2_PIX_FMT_PWC1 &&
DEVICE_USE_CODEC1(pdev->type)) ||
(fsize->pixel_format == V4L2_PIX_FMT_PWC2 &&
DEVICE_USE_CODEC23(pdev->type))) {
for (i = 0; i < PSZ_MAX; i++) {
if (pdev->image_mask & (1UL << i)) {
if (!(pdev->image_mask & (1UL << i)))
continue;
if (!index--) {
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
fsize->discrete.width = pwc_image_sizes[i].x;
fsize->discrete.height = pwc_image_sizes[i].y;
fsize->discrete.width = pwc_image_sizes[i][0];
fsize->discrete.height = pwc_image_sizes[i][1];
return 0;
}
}
}
} else if (fsize->index == 0 &&
((fsize->pixel_format == V4L2_PIX_FMT_PWC1 && DEVICE_USE_CODEC1(pdev->type)) ||
(fsize->pixel_format == V4L2_PIX_FMT_PWC2 && DEVICE_USE_CODEC23(pdev->type)))) {
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
fsize->discrete.width = pdev->abs_max.x;
fsize->discrete.height = pdev->abs_max.y;
return 0;
}
return -EINVAL;
}
......@@ -1092,8 +1084,8 @@ static int pwc_enum_frameintervals(struct file *file, void *fh,
unsigned int i;
for (i = 0; i < PSZ_MAX; i++) {
if (pwc_image_sizes[i].x == fival->width &&
pwc_image_sizes[i].y == fival->height) {
if (pwc_image_sizes[i][0] == fival->width &&
pwc_image_sizes[i][1] == fival->height) {
size = i;
break;
}
......
......@@ -107,6 +107,9 @@
#define FEATURE_CODEC1 0x0002
#define FEATURE_CODEC2 0x0004
#define MAX_WIDTH 640
#define MAX_HEIGHT 480
/* Ignore errors in the first N frames, to allow for startup delays */
#define FRAME_LOWMARK 5
......@@ -205,12 +208,6 @@ struct pwc_raw_frame {
__u8 rawframe[0]; /* frame_size = H / 4 * vbandlength */
} __packed;
/* structure for transferring x & y coordinates */
struct pwc_coord {
int x, y; /* guess what */
int size; /* size, or offset */
};
/* intermediate buffers with raw data from the USB cam */
struct pwc_frame_buf
{
......@@ -233,7 +230,6 @@ struct pwc_device
int type;
int release; /* release number */
int features; /* feature bits */
char serial[30]; /* serial number (string) */
/*** Video data ***/
struct file *capt_file; /* file doing video capture */
......@@ -286,10 +282,7 @@ struct pwc_device
* a gray or black border. view_min <= image <= view <= view_max;
*/
int image_mask; /* supported sizes */
struct pwc_coord view_min, view_max; /* minimum and maximum view */
struct pwc_coord abs_max; /* maximum supported size */
struct pwc_coord image, view; /* image and viewport size */
struct pwc_coord offset; /* offset of the viewport */
int width, height; /* current resolution */
#ifdef CONFIG_USB_PWC_INPUT_EVDEV
struct input_dev *button_dev; /* webcam snapshot button input */
......@@ -364,9 +357,9 @@ int pwc_test_n_set_capt_file(struct pwc_device *pdev, struct file *file);
/** Functions in pwc-misc.c */
/* sizes in pixels */
extern const struct pwc_coord pwc_image_sizes[PSZ_MAX];
extern const int pwc_image_sizes[PSZ_MAX][2];
int pwc_decode_size(struct pwc_device *pdev, int width, int height);
int pwc_get_size(struct pwc_device *pdev, int width, int height);
void pwc_construct(struct pwc_device *pdev);
/** Functions in pwc-ctrl.c */
......
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