From: Patrice Chotard <patrice.chotard@sfr.fr>
To: linux-media@vger.kernel.org
Cc: Theodore Kilgore <kilgota@banach.math.auburn.edu>
Subject: [PATCH] New Jeilin dual-mode camera support
Date: Mon, 14 Mar 2011 23:27:33 +0100 [thread overview]
Message-ID: <4D7E9655.106@sfr.fr> (raw)
[-- Attachment #1: Type: text/plain, Size: 74 bytes --]
Add a new Jeilin dual mode camera support and specific control settings.
[-- Attachment #2: Sportscam_DV15.patch --]
[-- Type: text/x-patch, Size: 14405 bytes --]
Signed-off-by: Patrice CHOTARD <patricechotard@free.fr>
Theodore Kilgore <kilgota@banach.math.auburn.edu>
---
drivers/media/video/gspca/jeilinj.c | 396 ++++++++++++++++++++++++++++++-----
1 files changed, 344 insertions(+), 52 deletions(-)
diff --git a/drivers/media/video/gspca/jeilinj.c b/drivers/media/video/gspca/jeilinj.c
index 06b777f..6b14028 100644
--- a/drivers/media/video/gspca/jeilinj.c
+++ b/drivers/media/video/gspca/jeilinj.c
@@ -6,6 +6,9 @@
*
* Copyright (C) 2009 Theodore Kilgore
*
+ * Sportscam DV15 support and control settings are
+ * Copyright (C) 2011 Patrice Chotard
+ *
* 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
@@ -24,6 +27,7 @@
#define MODULE_NAME "jeilinj"
#include <linux/workqueue.h>
+#include <linux/delay.h>
#include <linux/slab.h>
#include "gspca.h"
#include "jpeg.h"
@@ -34,6 +38,7 @@ MODULE_LICENSE("GPL");
/* Default timeouts, in ms */
#define JEILINJ_CMD_TIMEOUT 500
+#define JEILINJ_CMD_DELAY 160
#define JEILINJ_DATA_TIMEOUT 1000
/* Maximum transfer size to use. */
@@ -41,6 +46,18 @@ MODULE_LICENSE("GPL");
#define FRAME_HEADER_LEN 0x10
+enum {
+ SAKAR_57379,
+ SPORTSCAM_DV15,
+};
+
+#define CAMQUALITY_MIN 0 /* highest cam quality */
+#define CAMQUALITY_MAX 97 /* lowest cam quality */
+
+#define JPEGQUALITY_MIN 35
+#define JPEGQUALITY_MAX 85
+#define JPEGQUALITY_DEF 85
+
/* Structure to hold all of our device specific stuff */
struct sd {
struct gspca_dev gspca_dev; /* !! must be the first item */
@@ -48,23 +65,22 @@ struct sd {
/* Driver stuff */
struct work_struct work_struct;
struct workqueue_struct *work_thread;
- u8 quality; /* image quality */
- u8 jpegqual; /* webcam quality */
+ u8 camquality; /* webcam quality */
+ u8 jpegquality; /* jpeg quality */
u8 jpeg_hdr[JPEG_HDR_SZ];
+ u8 freq;
+ u8 type;
+ /* below variables are only used for SPORTSCAM_DV15 */
+ u8 autogain;
+ u8 cyan;
+ u8 magenta;
+ u8 yellow;
};
- struct jlj_command {
- unsigned char instruction[2];
- unsigned char ack_wanted;
- };
-
-/* AFAICT these cameras will only do 320x240. */
-static struct v4l2_pix_format jlj_mode[] = {
- { 320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
- .bytesperline = 320,
- .sizeimage = 320 * 240,
- .colorspace = V4L2_COLORSPACE_JPEG,
- .priv = 0}
+struct jlj_command {
+ unsigned char instruction[2];
+ unsigned char ack_wanted;
+ unsigned char delay;
};
/*
@@ -80,7 +96,7 @@ static int jlj_write2(struct gspca_dev *gspca_dev, unsigned char *command)
memcpy(gspca_dev->usb_buf, command, 2);
retval = usb_bulk_msg(gspca_dev->dev,
usb_sndbulkpipe(gspca_dev->dev, 3),
- gspca_dev->usb_buf, 2, NULL, 500);
+ gspca_dev->usb_buf, 2, NULL, JEILINJ_CMD_TIMEOUT);
if (retval < 0)
err("command write [%02x] error %d",
gspca_dev->usb_buf[0], retval);
@@ -94,7 +110,7 @@ static int jlj_read1(struct gspca_dev *gspca_dev, unsigned char response)
retval = usb_bulk_msg(gspca_dev->dev,
usb_rcvbulkpipe(gspca_dev->dev, 0x84),
- gspca_dev->usb_buf, 1, NULL, 500);
+ gspca_dev->usb_buf, 1, NULL, JEILINJ_CMD_TIMEOUT);
response = gspca_dev->usb_buf[0];
if (retval < 0)
err("read command [%02x] error %d",
@@ -102,49 +118,282 @@ static int jlj_read1(struct gspca_dev *gspca_dev, unsigned char response)
return retval;
}
+static void setfreq(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct jlj_command freq_commands[] = {
+ {{0x71, 0x80}, 0, 0},
+ {{0x70, 0x07}, 0, 0}
+ };
+
+ if (sd->freq)
+ freq_commands[0].instruction[1] & (sd->freq >> 1);
+
+ jlj_write2(gspca_dev, freq_commands[0].instruction);
+ jlj_write2(gspca_dev, freq_commands[1].instruction);
+}
+
+static void setcamquality(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct jlj_command quality_commands[] = {
+ {{0x71, 0x1E}, 0, 0},
+ {{0x70, 0x06}, 0, 0}
+ };
+ u8 jpegquality;
+ u8 temp;
+
+ quality_commands[0].instruction[1] += sd->camquality;
+
+ jlj_write2(gspca_dev, quality_commands[0].instruction);
+ jlj_write2(gspca_dev, quality_commands[1].instruction);
+
+ /* adapt quantification table to camera quality */
+ temp = ((JPEGQUALITY_MAX - JPEGQUALITY_MIN) * sd->camquality)
+ / CAMQUALITY_MAX;
+ jpegquality = JPEGQUALITY_MAX - temp;
+ jpeg_set_qual(sd->jpeg_hdr, jpegquality);
+}
+
+static void setautogain(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct jlj_command autogain_commands[] = {
+ {{0x94, 0x02}, 0, 0},
+ {{0xcf, 0x00}, 0, 0}
+ };
+
+ autogain_commands[1].instruction[1] = (sd->autogain << 4);
+
+ jlj_write2(gspca_dev, autogain_commands[0].instruction);
+ jlj_write2(gspca_dev, autogain_commands[1].instruction);
+}
+
+static void setcyan(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct jlj_command setcyan_commands[] = {
+ {{0x94, 0x02}, 0, 0},
+ {{0xe6, 0x00}, 0, 0}
+ };
+
+ setcyan_commands[1].instruction[1] = sd->cyan;
+
+ jlj_write2(gspca_dev, setcyan_commands[0].instruction);
+ jlj_write2(gspca_dev, setcyan_commands[1].instruction);
+}
+
+static void setmagenta(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct jlj_command setmagenta_commands[] = {
+ {{0x94, 0x02}, 0, 0},
+ {{0xe7, 0x00}, 0, 0}
+ };
+
+ setmagenta_commands[1].instruction[1] = sd->magenta;
+
+ jlj_write2(gspca_dev, setmagenta_commands[0].instruction);
+ jlj_write2(gspca_dev, setmagenta_commands[1].instruction);
+}
+
+static void setyellow(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct jlj_command setyellow_commands[] = {
+ {{0x94, 0x02}, 0, 0},
+ {{0xe9, 0x00}, 0, 0}
+ };
+
+ setyellow_commands[1].instruction[1] = sd->yellow;
+
+ jlj_write2(gspca_dev, setyellow_commands[0].instruction);
+ jlj_write2(gspca_dev, setyellow_commands[1].instruction);
+}
+
+/* Functions to get and set a control value */
+#define SD_SETGET(thename) \
+static int sd_set##thename(struct gspca_dev *gspca_dev, s32 val)\
+{\
+ struct sd *sd = (struct sd *) gspca_dev;\
+\
+ sd->thename = val;\
+ if (gspca_dev->streaming)\
+ set##thename(gspca_dev);\
+ return 0;\
+} \
+static int sd_get##thename(struct gspca_dev *gspca_dev, s32 *val)\
+{\
+ struct sd *sd = (struct sd *) gspca_dev;\
+\
+ *val = sd->thename;\
+ return 0;\
+}
+
+SD_SETGET(freq);
+SD_SETGET(camquality);
+SD_SETGET(autogain);
+SD_SETGET(cyan);
+SD_SETGET(magenta);
+SD_SETGET(yellow);
+
+static struct ctrl sd_ctrls[] = {
+ {
+ {
+ .id = V4L2_CID_POWER_LINE_FREQUENCY,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .name = "Light frequency filter",
+ .minimum = V4L2_CID_POWER_LINE_FREQUENCY_DISABLED, /* 0 */
+ .maximum = V4L2_CID_POWER_LINE_FREQUENCY_60HZ, /* 2 */
+ .step = 1,
+ .default_value = V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+ },
+ .set = sd_setfreq,
+ .get = sd_getfreq,
+ },
+ {
+ {
+ .id = V4L2_CID_AUTOGAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Automatic Gain (and Exposure)",
+ .minimum = 0,
+ .maximum = 3,
+ .step = 1,
+#define AUTOGAIN_DEF 0
+ .default_value = AUTOGAIN_DEF,
+ },
+ .set = sd_setautogain,
+ .get = sd_getautogain,
+ },
+ {
+ {
+#define V4L2_CID_CAMQUALITY (V4L2_CID_USER_BASE + 1)
+ .id = V4L2_CID_CAMQUALITY,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Image quality",
+ .minimum = CAMQUALITY_MIN,
+ .maximum = CAMQUALITY_MAX,
+ .step = 1,
+#define CAMQUALITY_DEF 0
+ .default_value = CAMQUALITY_DEF,
+ },
+ .set = sd_setcamquality,
+ .get = sd_getcamquality,
+ },
+ {
+ {
+#define V4L2_CID_CYAN_BALANCE (V4L2_CID_USER_BASE + 2)
+ .id = V4L2_CID_CYAN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Cyan balance",
+ .minimum = 0,
+ .maximum = 3,
+ .step = 1,
+#define CYAN_BALANCE_DEF 2
+ .default_value = CYAN_BALANCE_DEF,
+ },
+ .set = sd_setcyan,
+ .get = sd_getcyan,
+ },
+ {
+ {
+#define V4L2_CID_MAGENTA_BALANCE (V4L2_CID_USER_BASE + 3)
+ .id = V4L2_CID_MAGENTA_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Magenta balance",
+ .minimum = 0,
+ .maximum = 3,
+ .step = 1,
+#define MAGENTA_BALANCE_DEF 2
+ .default_value = MAGENTA_BALANCE_DEF,
+ },
+ .set = sd_setmagenta,
+ .get = sd_getmagenta,
+ },
+ {
+ {
+#define V4L2_CID_YELLOW_BALANCE (V4L2_CID_USER_BASE + 4)
+ .id = V4L2_CID_YELLOW_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Yellow balance",
+ .minimum = 0,
+ .maximum = 3,
+ .step = 1,
+#define YELLOW_BALANCE_DEF 2
+ .default_value = YELLOW_BALANCE_DEF,
+ },
+ .set = sd_setyellow,
+ .get = sd_getyellow,
+ },
+};
+
+static struct v4l2_pix_format jlj_mode[] = {
+ { 320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+ .bytesperline = 320,
+ .sizeimage = 320 * 240,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ .priv = 0},
+ { 640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+ .bytesperline = 640,
+ .sizeimage = 640 * 480,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ .priv = 0}
+};
+
static int jlj_start(struct gspca_dev *gspca_dev)
{
+ struct sd *sd = (struct sd *) gspca_dev;
int i;
int retval = -1;
+ int start_commands_size;
u8 response = 0xff;
struct jlj_command start_commands[] = {
- {{0x71, 0x81}, 0},
- {{0x70, 0x05}, 0},
- {{0x95, 0x70}, 1},
- {{0x71, 0x81}, 0},
- {{0x70, 0x04}, 0},
- {{0x95, 0x70}, 1},
- {{0x71, 0x00}, 0},
- {{0x70, 0x08}, 0},
- {{0x95, 0x70}, 1},
- {{0x94, 0x02}, 0},
- {{0xde, 0x24}, 0},
- {{0x94, 0x02}, 0},
- {{0xdd, 0xf0}, 0},
- {{0x94, 0x02}, 0},
- {{0xe3, 0x2c}, 0},
- {{0x94, 0x02}, 0},
- {{0xe4, 0x00}, 0},
- {{0x94, 0x02}, 0},
- {{0xe5, 0x00}, 0},
- {{0x94, 0x02}, 0},
- {{0xe6, 0x2c}, 0},
- {{0x94, 0x03}, 0},
- {{0xaa, 0x00}, 0},
- {{0x71, 0x1e}, 0},
- {{0x70, 0x06}, 0},
- {{0x71, 0x80}, 0},
- {{0x70, 0x07}, 0}
+ {{0x71, 0x81}, 0, 0},
+ {{0x70, 0x05}, 0, JEILINJ_CMD_DELAY},
+ {{0x95, 0x70}, 1, 0},
+ {{0x71, 0x81 - gspca_dev->curr_mode}, 0, 0},
+ {{0x70, 0x04}, 0, JEILINJ_CMD_DELAY},
+ {{0x95, 0x70}, 1, 0},
+ {{0x71, 0x00}, 0, 0}, /* start streaming ??*/
+ {{0x70, 0x08}, 0, JEILINJ_CMD_DELAY},
+ {{0x95, 0x70}, 1, 0},
+ {{0x94, 0x02}, 0, 0},
+ {{0xde, 0x24}, 0, 0},
+ {{0x94, 0x02}, 0, 0},
+ {{0xdd, 0xf0}, 0, 0},
+ {{0x94, 0x02}, 0, 0},
+ {{0xe3, 0x2c}, 0, 0},
+ {{0x94, 0x02}, 0, 0},
+ {{0xe4, 0x00}, 0, 0},
+ {{0x94, 0x02}, 0, 0},
+ {{0xe5, 0x00}, 0, 0},
+ {{0x94, 0x02}, 0, 0},
+ {{0xe6, 0x2c}, 0, 0},
+ {{0x94, 0x03}, 0, 0},
+ {{0xaa, 0x00}, 0, 0}
};
- for (i = 0; i < ARRAY_SIZE(start_commands); i++) {
+
+ /* Under Windows, USB spy shows that only the 9 first start
+ * commands are used for SPORTSCAM_DV15 webcam
+ */
+ if (sd->type == SPORTSCAM_DV15)
+ start_commands_size = 9;
+ else
+ start_commands_size = ARRAY_SIZE(start_commands);
+
+ for (i = 0; i < start_commands_size; i++) {
retval = jlj_write2(gspca_dev, start_commands[i].instruction);
if (retval < 0)
return retval;
+ if (start_commands[i].delay)
+ mdelay(start_commands[i].delay);
if (start_commands[i].ack_wanted)
retval = jlj_read1(gspca_dev, response);
if (retval < 0)
return retval;
}
+ setcamquality(gspca_dev);
+ setfreq(gspca_dev);
PDEBUG(D_ERR, "jlj_start retval is %d", retval);
return retval;
}
@@ -256,13 +505,15 @@ static int sd_config(struct gspca_dev *gspca_dev,
struct cam *cam = &gspca_dev->cam;
struct sd *dev = (struct sd *) gspca_dev;
- dev->quality = 85;
- dev->jpegqual = 85;
+ dev->type = id->driver_info;
+ dev->camquality = CAMQUALITY_DEF;
+ dev->jpegquality = JPEGQUALITY_DEF;
+ dev->freq = V4L2_CID_POWER_LINE_FREQUENCY_60HZ;
PDEBUG(D_PROBE,
"JEILINJ camera detected"
" (vid/pid 0x%04X:0x%04X)", id->idVendor, id->idProduct);
cam->cam_mode = jlj_mode;
- cam->nmodes = 1;
+ cam->nmodes = ARRAY_SIZE(jlj_mode);
cam->bulk = 1;
/* We don't use the buffer gspca allocates so make it small. */
cam->bulk_size = 32;
@@ -299,8 +550,10 @@ static int sd_start(struct gspca_dev *gspca_dev)
/* create the JPEG header */
jpeg_define(dev->jpeg_hdr, gspca_dev->height, gspca_dev->width,
0x21); /* JPEG 422 */
- jpeg_set_qual(dev->jpeg_hdr, dev->quality);
- PDEBUG(D_STREAM, "Start streaming at 320x240");
+ dev->jpegquality = JPEGQUALITY_DEF;
+ jpeg_set_qual(dev->jpeg_hdr, dev->jpegquality);
+ PDEBUG(D_STREAM, "Start streaming at %dx%d",
+ gspca_dev->width, gspca_dev->height);
ret = jlj_start(gspca_dev);
if (ret < 0) {
PDEBUG(D_ERR, "Start streaming command failed");
@@ -315,14 +568,36 @@ static int sd_start(struct gspca_dev *gspca_dev)
/* Table of supported USB devices */
static const struct usb_device_id device_table[] = {
- {USB_DEVICE(0x0979, 0x0280)},
+ {USB_DEVICE(0x0979, 0x0280), .driver_info = SAKAR_57379},
+ {USB_DEVICE(0x0979, 0x0270), .driver_info = SPORTSCAM_DV15},
{}
};
MODULE_DEVICE_TABLE(usb, device_table);
+static int sd_querymenu(struct gspca_dev *gspca_dev,
+ struct v4l2_querymenu *menu)
+{
+ switch (menu->id) {
+ case V4L2_CID_POWER_LINE_FREQUENCY:
+ switch (menu->index) {
+ case 0: /* V4L2_CID_POWER_LINE_FREQUENCY_DISABLED */
+ strcpy((char *) menu->name, "NoFliker");
+ return 0;
+ case 1: /* V4L2_CID_POWER_LINE_FREQUENCY_50HZ */
+ strcpy((char *) menu->name, "50 Hz");
+ return 0;
+ case 2: /* V4L2_CID_POWER_LINE_FREQUENCY_60HZ */
+ strcpy((char *) menu->name, "60 Hz");
+ return 0;
+ }
+ break;
+ }
+ return -EINVAL;
+}
+
/* sub-driver description */
-static const struct sd_desc sd_desc = {
+static const struct sd_desc sd_desc_sakar_57379 = {
.name = MODULE_NAME,
.config = sd_config,
.init = sd_init,
@@ -330,12 +605,29 @@ static const struct sd_desc sd_desc = {
.stop0 = sd_stop0,
};
+/* sub-driver description */
+static const struct sd_desc sd_desc_sportscam_dv15 = {
+ .name = MODULE_NAME,
+ .config = sd_config,
+ .init = sd_init,
+ .start = sd_start,
+ .stop0 = sd_stop0,
+ .ctrls = sd_ctrls,
+ .nctrls = ARRAY_SIZE(sd_ctrls),
+ .querymenu = sd_querymenu,
+};
+
+static const struct sd_desc *sd_desc[2] = {
+ &sd_desc_sakar_57379,
+ &sd_desc_sportscam_dv15
+};
+
/* -- device connect -- */
static int sd_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
return gspca_dev_probe(intf, id,
- &sd_desc,
+ sd_desc[id->driver_info],
sizeof(struct sd),
THIS_MODULE);
}
--
1.7.0.4
next reply other threads:[~2011-03-14 22:27 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-03-14 22:27 Patrice Chotard [this message]
-- strict thread matches above, loose matches on Subject: below --
2011-03-16 20:06 [PATCH] New Jeilin dual-mode camera support Patrice Chotard
2011-03-17 10:28 ` Jean-Francois Moine
2011-03-17 23:32 ` Patrice Chotard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4D7E9655.106@sfr.fr \
--to=patrice.chotard@sfr.fr \
--cc=kilgota@banach.math.auburn.edu \
--cc=linux-media@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.