Commit 4c98834a authored by Erik Andren's avatar Erik Andren Committed by Mauro Carvalho Chehab

V4L/DVB (10048): gspca - stv06xx: New subdriver.

Signed-off-by: default avatarErik Andren <erik.andren@gmail.com>
Signed-off-by: default avatarJean-Francois Moine <moinejf@free.fr>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 15f64864
......@@ -50,6 +50,9 @@ ov519 045e:028c Micro$oft xbox cam
spca508 0461:0815 Micro Innovation IC200
sunplus 0461:0821 Fujifilm MV-1
zc3xx 0461:0a00 MicroInnovation WebCam320
stv06xx 046d:0840 QuickCam Express
stv06xx 046d:0850 LEGO cam / QuickCam Web
stv06xx 046d:0870 Dexxa WebCam USB
spca500 046d:0890 Logitech QuickCam traveler
vc032x 046d:0892 Logitech Orbicam
vc032x 046d:0896 Logitech Orbicam
......
......@@ -18,6 +18,7 @@ menuconfig USB_GSPCA
if USB_GSPCA && VIDEO_V4L2
source "drivers/media/video/gspca/m5602/Kconfig"
source "drivers/media/video/gspca/stv06xx/Kconfig"
config USB_GSPCA_CONEX
tristate "Conexant Camera Driver"
......
......@@ -47,4 +47,4 @@ gspca_vc032x-objs := vc032x.o
gspca_zc3xx-objs := zc3xx.o
obj-$(CONFIG_USB_M5602) += m5602/
obj-$(CONFIG_USB_STV06XX) += stv06xx/
config USB_STV06XX
tristate "STV06XX USB Camera Driver"
depends on USB_GSPCA
help
Say Y here if you want support for cameras based on
the ST STV06XX chip.
To compile this driver as a module, choose M here: the
module will be called gspca_stv06xx.
obj-$(CONFIG_USB_STV06XX) += gspca_stv06xx.o
gspca_stv06xx-objs := stv06xx.o \
stv06xx_vv6410.o \
stv06xx_hdcs.o \
stv06xx_pb0100.o
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
#include "stv06xx_sensor.h"
MODULE_AUTHOR("Erik Andrén");
MODULE_DESCRIPTION("STV06XX USB Camera Driver");
MODULE_LICENSE("GPL");
int dump_bridge;
int dump_sensor;
int stv06xx_write_bridge(struct sd *sd, u16 address, u16 i2c_data)
{
int err;
struct usb_device *udev = sd->gspca_dev.dev;
__u8 *buf = sd->gspca_dev.usb_buf;
u8 len = (i2c_data > 0xff) ? 2 : 1;
buf[0] = i2c_data & 0xff;
buf[1] = (i2c_data >> 8) & 0xff;
err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
0x04, 0x40, address, 0, buf, len,
STV06XX_URB_MSG_TIMEOUT);
PDEBUG(D_CONF, "Written 0x%x to address 0x%x, status: %d",
i2c_data, address, err);
return (err < 0) ? err : 0;
}
int stv06xx_read_bridge(struct sd *sd, u16 address, u8 *i2c_data)
{
int err;
struct usb_device *udev = sd->gspca_dev.dev;
__u8 *buf = sd->gspca_dev.usb_buf;
err = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
0x04, 0xc0, address, 0, buf, 1,
STV06XX_URB_MSG_TIMEOUT);
*i2c_data = buf[0];
PDEBUG(D_CONF, "Read 0x%x from address 0x%x, status %d",
*i2c_data, address, err);
return (err < 0) ? err : 0;
}
/* Wraps the normal write sensor bytes / words functions for writing a
single value */
int stv06xx_write_sensor(struct sd *sd, u8 address, u16 value)
{
if (sd->sensor->i2c_len == 2) {
u16 data[2] = { address, value };
return stv06xx_write_sensor_words(sd, data, 1);
} else {
u8 data[2] = { address, value };
return stv06xx_write_sensor_bytes(sd, data, 1);
}
}
static int stv06xx_write_sensor_finish(struct sd *sd)
{
int err = 0;
if (IS_850(sd)) {
struct usb_device *udev = sd->gspca_dev.dev;
__u8 *buf = sd->gspca_dev.usb_buf;
/* Quickam Web needs an extra packet */
buf[0] = 0;
err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
0x04, 0x40, 0x1704, 0, buf, 1,
STV06XX_URB_MSG_TIMEOUT);
}
return (err < 0) ? err : 0;
}
int stv06xx_write_sensor_bytes(struct sd *sd, const u8 *data, u8 len)
{
int err, i, j;
struct usb_device *udev = sd->gspca_dev.dev;
__u8 *buf = sd->gspca_dev.usb_buf;
PDEBUG(D_USBO, "I2C: Command buffer contains %d entries", len);
for (i = 0; i < len;) {
/* Build the command buffer */
memset(buf, 0, I2C_BUFFER_LENGTH);
for (j = 0; j < I2C_MAX_BYTES && i < len; j++, i++) {
buf[j] = data[2*i];
buf[0x10 + j] = data[2*i+1];
PDEBUG(D_USBO, "I2C: Writing 0x%02x to reg 0x%02x",
data[2*i+1], data[2*i]);
}
buf[0x20] = sd->sensor->i2c_addr;
buf[0x21] = j - 1; /* Number of commands to send - 1 */
buf[0x22] = I2C_WRITE_CMD;
err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
0x04, 0x40, 0x0400, 0, buf,
I2C_BUFFER_LENGTH,
STV06XX_URB_MSG_TIMEOUT);
if (err < 0)
return err;
}
return stv06xx_write_sensor_finish(sd);
}
int stv06xx_write_sensor_words(struct sd *sd, const u16 *data, u8 len)
{
int err, i, j;
struct usb_device *udev = sd->gspca_dev.dev;
__u8 *buf = sd->gspca_dev.usb_buf;
PDEBUG(D_USBO, "I2C: Command buffer contains %d entries", len);
for (i = 0; i < len;) {
/* Build the command buffer */
memset(buf, 0, I2C_BUFFER_LENGTH);
for (j = 0; j < I2C_MAX_WORDS && i < len; j++, i++) {
buf[j] = data[2*i];
buf[0x10 + j * 2] = data[2*i+1];
buf[0x10 + j * 2 + 1] = data[2*i+1] >> 8;
PDEBUG(D_USBO, "I2C: Writing 0x%04x to reg 0x%02x",
data[2*i+1], data[2*i]);
}
buf[0x20] = sd->sensor->i2c_addr;
buf[0x21] = j - 1; /* Number of commands to send - 1 */
buf[0x22] = I2C_WRITE_CMD;
err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
0x04, 0x40, 0x0400, 0, buf,
I2C_BUFFER_LENGTH,
STV06XX_URB_MSG_TIMEOUT);
if (err < 0)
return err;
}
return stv06xx_write_sensor_finish(sd);
}
int stv06xx_read_sensor(struct sd *sd, const u8 address, u16 *value)
{
int err;
struct usb_device *udev = sd->gspca_dev.dev;
__u8 *buf = sd->gspca_dev.usb_buf;
err = stv06xx_write_bridge(sd, STV_I2C_FLUSH, sd->sensor->i2c_flush);
if (err < 0)
return err;
/* Clear mem */
memset(buf, 0, I2C_BUFFER_LENGTH);
buf[0] = address;
buf[0x20] = sd->sensor->i2c_addr;
buf[0x21] = 0;
/* Read I2C register */
buf[0x22] = I2C_READ_CMD;
err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
0x04, 0x40, 0x1400, 0, buf, I2C_BUFFER_LENGTH,
STV06XX_URB_MSG_TIMEOUT);
if (err < 0) {
PDEBUG(D_ERR, "I2C Read: error writing address: %d", err);
return err;
}
err = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
0x04, 0xc0, 0x1410, 0, buf, sd->sensor->i2c_len,
STV06XX_URB_MSG_TIMEOUT);
if (sd->sensor->i2c_len == 2)
*value = buf[0] | (buf[1] << 8);
else
*value = buf[0];
PDEBUG(D_USBO, "I2C: Read 0x%x from address 0x%x, status: %d",
*value, address, err);
return (err < 0) ? err : 0;
}
/* Dumps all bridge registers */
static void stv06xx_dump_bridge(struct sd *sd)
{
int i;
u8 data, buf;
info("Dumping all stv06xx bridge registers");
for (i = 0x1400; i < 0x160f; i++) {
stv06xx_read_bridge(sd, i, &data);
info("Read 0x%x from address 0x%x", data, i);
}
for (i = 0x1400; i < 0x160f; i++) {
stv06xx_read_bridge(sd, i, &data);
buf = data;
stv06xx_write_bridge(sd, i, 0xff);
stv06xx_read_bridge(sd, i, &data);
if (data == 0xff)
info("Register 0x%x is read/write", i);
else if (data != buf)
info("Register 0x%x is read/write,"
"but only partially", i);
else
info("Register 0x%x is read-only", i);
stv06xx_write_bridge(sd, i, buf);
}
}
/* this function is called at probe and resume time */
static int stv06xx_init(struct gspca_dev *gspca_dev)
{
struct sd *sd = (struct sd *) gspca_dev;
int err;
PDEBUG(D_PROBE, "Initializing camera");
/* Let the usb init settle for a bit
before performing the initialization */
msleep(250);
err = sd->sensor->init(sd);
if (dump_sensor)
sd->sensor->dump(sd);
return (err < 0) ? err : 0;
}
/* Start the camera */
static int stv06xx_start(struct gspca_dev *gspca_dev)
{
struct sd *sd = (struct sd *) gspca_dev;
int err;
/* Prepare the sensor for start */
err = sd->sensor->start(sd);
if (err < 0)
goto out;
/* Start isochronous streaming */
err = stv06xx_write_bridge(sd, STV_ISO_ENABLE, 1);
out:
if (err < 0)
PDEBUG(D_STREAM, "Starting stream failed");
else
PDEBUG(D_STREAM, "Started streaming");
return (err < 0) ? err : 0;
}
static void stv06xx_stopN(struct gspca_dev *gspca_dev)
{
int err;
struct sd *sd = (struct sd *) gspca_dev;
/* stop ISO-streaming */
err = stv06xx_write_bridge(sd, STV_ISO_ENABLE, 0);
if (err < 0)
goto out;
err = sd->sensor->stop(sd);
if (err < 0)
goto out;
out:
if (err < 0)
PDEBUG(D_STREAM, "Failed to stop stream");
else
PDEBUG(D_STREAM, "Stopped streaming");
}
/*
* Analyse an USB packet of the data stream and store it appropriately.
* Each packet contains an integral number of chunks. Each chunk has
* 2-bytes identification, followed by 2-bytes that describe the chunk
* length. Known/guessed chunk identifications are:
* 8001/8005/C001/C005 - Begin new frame
* 8002/8006/C002/C006 - End frame
* 0200/4200 - Contains actual image data, bayer or compressed
* 0005 - 11 bytes of unknown data
* 0100 - 2 bytes of unknown data
* The 0005 and 0100 chunks seem to appear only in compressed stream.
*/
static void stv06xx_pkt_scan(struct gspca_dev *gspca_dev,
struct gspca_frame *frame, /* target */
__u8 *data, /* isoc packet */
int len) /* iso packet length */
{
PDEBUG(D_PACK, "Packet of length %d arrived", len);
/* A packet may contain several frames
loop until the whole packet is reached */
while (len) {
int id, chunk_len;
if (len < 4) {
PDEBUG(D_PACK, "Packet is smaller than 4 bytes");
return;
}
/* Capture the id */
id = (data[0] << 8) | data[1];
/* Capture the chunk length */
chunk_len = (data[2] << 8) | data[3];
PDEBUG(D_PACK, "Chunk id: %x, length: %d", id, chunk_len);
data += 4;
len -= 4;
if (len < chunk_len) {
PDEBUG(D_ERR, "URB packet length is smaller"
" than the specified chunk length");
return;
}
switch (id) {
case 0x0200:
case 0x4200:
PDEBUG(D_PACK, "Frame data packet detected");
gspca_frame_add(gspca_dev, INTER_PACKET, frame,
data, chunk_len);
break;
case 0x8001:
case 0x8005:
case 0xc001:
case 0xc005:
PDEBUG(D_PACK, "Starting new frame");
/* Create a new frame, chunk length should be zero */
gspca_frame_add(gspca_dev, FIRST_PACKET,
frame, data, 0);
if (chunk_len)
PDEBUG(D_ERR, "Chunk length is "
"non-zero on a SOF");
break;
case 0x8002:
case 0x8006:
case 0xc002:
PDEBUG(D_PACK, "End of frame detected");
/* Complete the last frame (if any) */
gspca_frame_add(gspca_dev, LAST_PACKET, frame, data, 0);
if (chunk_len)
PDEBUG(D_ERR, "Chunk length is "
"non-zero on a EOF");
break;
case 0x0005:
PDEBUG(D_PACK, "Chunk 0x005 detected");
/* Unknown chunk with 11 bytes of data,
occurs just before end of each frame
in compressed mode */
break;
case 0x0100:
PDEBUG(D_PACK, "Chunk 0x0100 detected");
/* Unknown chunk with 2 bytes of data,
occurs 2-3 times per USB interrupt */
break;
default:
PDEBUG(D_PACK, "Unknown chunk %d detected", id);
/* Unknown chunk */
}
data += chunk_len;
len -= chunk_len;
}
}
static int stv06xx_config(struct gspca_dev *gspca_dev,
const struct usb_device_id *id);
/* sub-driver description */
static const struct sd_desc sd_desc = {
.name = MODULE_NAME,
.config = stv06xx_config,
.init = stv06xx_init,
.start = stv06xx_start,
.stopN = stv06xx_stopN,
.pkt_scan = stv06xx_pkt_scan
};
/* This function is called at probe time */
static int stv06xx_config(struct gspca_dev *gspca_dev,
const struct usb_device_id *id)
{
struct sd *sd = (struct sd *) gspca_dev;
struct cam *cam;
PDEBUG(D_PROBE, "Configuring camera");
cam = &gspca_dev->cam;
cam->epaddr = STV_ISOC_ENDPOINT_ADDR;
sd->desc = sd_desc;
gspca_dev->sd_desc = &sd->desc;
if (dump_bridge)
stv06xx_dump_bridge(sd);
sd->sensor = &stv06xx_sensor_vv6410;
if (!sd->sensor->probe(sd))
return 0;
sd->sensor = &stv06xx_sensor_hdcs1x00;
if (!sd->sensor->probe(sd))
return 0;
sd->sensor = &stv06xx_sensor_hdcs1020;
if (!sd->sensor->probe(sd))
return 0;
sd->sensor = &stv06xx_sensor_pb0100;
if (!sd->sensor->probe(sd))
return 0;
sd->sensor = NULL;
return -ENODEV;
}
/* -- module initialisation -- */
static const __devinitdata struct usb_device_id device_table[] = {
{USB_DEVICE(0x046d, 0x0840)}, /* QuickCam Express */
{USB_DEVICE(0x046d, 0x0850)}, /* LEGO cam / QuickCam Web */
{USB_DEVICE(0x046d, 0x0870)}, /* Dexxa WebCam USB */
{}
};
MODULE_DEVICE_TABLE(usb, device_table);
/* -- device connect -- */
static int sd_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
PDEBUG(D_PROBE, "Probing for a stv06xx device");
return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
THIS_MODULE);
}
void sd_disconnect(struct usb_interface *intf)
{
struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
struct sd *sd = (struct sd *) gspca_dev;
PDEBUG(D_PROBE, "Disconnecting the stv06xx device");
if (sd->sensor->disconnect)
sd->sensor->disconnect(sd);
gspca_disconnect(intf);
}
static struct usb_driver sd_driver = {
.name = MODULE_NAME,
.id_table = device_table,
.probe = sd_probe,
.disconnect = sd_disconnect,
#ifdef CONFIG_PM
.suspend = gspca_suspend,
.resume = gspca_resume,
#endif
};
/* -- module insert / remove -- */
static int __init sd_mod_init(void)
{
if (usb_register(&sd_driver) < 0)
return -1;
PDEBUG(D_PROBE, "registered");
return 0;
}
static void __exit sd_mod_exit(void)
{
usb_deregister(&sd_driver);
PDEBUG(D_PROBE, "deregistered");
}
module_init(sd_mod_init);
module_exit(sd_mod_exit);
module_param(dump_bridge, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(dump_bridge, "Dumps all usb bridge registers at startup");
module_param(dump_sensor, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(dump_sensor, "Dumps all sensor registers at startup");
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
#ifndef STV06XX_H_
#define STV06XX_H_
#include "gspca.h"
#define MODULE_NAME "STV06xx"
#define STV_ISOC_ENDPOINT_ADDR 0x81
#ifndef V4L2_PIX_FMT_SGRBG8
#define V4L2_PIX_FMT_SGRBG8 v4l2_fourcc('G', 'R', 'B', 'G')
#endif
#define STV_REG23 0x0423
/* Control registers of the STV0600 ASIC */
#define STV_I2C_PARTNER 0x1420
#define STV_I2C_VAL_REG_VAL_PAIRS_MIN1 0x1421
#define STV_I2C_READ_WRITE_TOGGLE 0x1422
#define STV_I2C_FLUSH 0x1423
#define STV_I2C_SUCC_READ_REG_VALS 0x1424
#define STV_ISO_ENABLE 0x1440
#define STV_SCAN_RATE 0x1443
#define STV_LED_CTRL 0x1445
#define STV_STV0600_EMULATION 0x1446
#define STV_REG00 0x1500
#define STV_REG01 0x1501
#define STV_REG02 0x1502
#define STV_REG03 0x1503
#define STV_REG04 0x1504
#define STV_ISO_SIZE_L 0x15c1
#define STV_ISO_SIZE_H 0x15c2
/* Refers to the CIF 352x288 and QCIF 176x144 */
/* 1: 288 lines, 2: 144 lines */
#define STV_Y_CTRL 0x15c3
/* 0xa: 352 columns, 0x6: 176 columns */
#define STV_X_CTRL 0x1680
#define STV06XX_URB_MSG_TIMEOUT 5000
#define I2C_MAX_BYTES 16
#define I2C_MAX_WORDS 8
#define I2C_BUFFER_LENGTH 0x23
#define I2C_READ_CMD 3
#define I2C_WRITE_CMD 1
#define LED_ON 1
#define LED_OFF 0
/* STV06xx device descriptor */
struct sd {
struct gspca_dev gspca_dev;
/* A pointer to the currently connected sensor */
const struct stv06xx_sensor *sensor;
/* A pointer to the sd_desc struct */
struct sd_desc desc;
/* Sensor private data */
void *sensor_priv;
};
int stv06xx_write_bridge(struct sd *sd, u16 address, u16 i2c_data);
int stv06xx_read_bridge(struct sd *sd, u16 address, u8 *i2c_data);
int stv06xx_write_sensor_bytes(struct sd *sd, const u8 *data, u8 len);
int stv06xx_write_sensor_words(struct sd *sd, const u16 *data, u8 len);
int stv06xx_read_sensor(struct sd *sd, const u8 address, u16 *value);
int stv06xx_write_sensor(struct sd *sd, u8 address, u16 value);
#endif
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
* Copyright (c) 2008 Chia-I Wu
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
#include "stv06xx_hdcs.h"
enum hdcs_power_state {
HDCS_STATE_SLEEP,
HDCS_STATE_IDLE,
HDCS_STATE_RUN
};
/* no lock? */
struct hdcs {
enum hdcs_power_state state;
int w, h;
/* visible area of the sensor array */
struct {
int left, top;
int width, height;
int border;
} array;
struct {
/* Column timing overhead */
u8 cto;
/* Column processing overhead */
u8 cpo;
/* Row sample period constant */
u16 rs;
/* Exposure reset duration */
u16 er;
} exp;
int psmp;
};
static int hdcs_reg_write_seq(struct sd *sd, u8 reg, u8 *vals, u8 len)
{
u8 regs[I2C_MAX_BYTES * 2];
int i;
if (unlikely((len <= 0) || (len >= I2C_MAX_BYTES) ||
(reg + len > 0xff)))
return -EINVAL;
for (i = 0; i < len; i++, reg++) {
regs[2*i] = reg;
regs[2*i+1] = vals[i];
}
return stv06xx_write_sensor_bytes(sd, regs, len);
}
static int hdcs_set_state(struct sd *sd, enum hdcs_power_state state)
{
struct hdcs *hdcs = sd->sensor_priv;
u8 val;
int ret;
if (hdcs->state == state)
return 0;
/* we need to go idle before running or sleeping */
if (hdcs->state != HDCS_STATE_IDLE) {
ret = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 0);
if (ret)
return ret;
}
hdcs->state = HDCS_STATE_IDLE;
if (state == HDCS_STATE_IDLE)
return 0;
switch (state) {
case HDCS_STATE_SLEEP:
val = HDCS_SLEEP_MODE;
break;
case HDCS_STATE_RUN:
val = HDCS_RUN_ENABLE;
break;
default:
return -EINVAL;
}
ret = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), val);
if (ret < 0)
hdcs->state = state;
return ret;
}
static int hdcs_reset(struct sd *sd)
{
struct hdcs *hdcs = sd->sensor_priv;
int err;
err = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 1);
if (err < 0)
return err;
err = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 0);
if (err < 0)
hdcs->state = HDCS_STATE_IDLE;
return err;
}
static int hdcs_get_exposure(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
struct hdcs *hdcs = sd->sensor_priv;
/* Column time period */
int ct;
/* Column processing period */
int cp;
/* Row processing period */
int rp;
int cycles;
int err;
int rowexp;
u16 data[2];
err = stv06xx_read_sensor(sd, HDCS_ROWEXPL, &data[0]);
if (err < 0)
return err;
err = stv06xx_read_sensor(sd, HDCS_ROWEXPH, &data[1]);
if (err < 0)
return err;
rowexp = (data[1] << 8) | data[0];
ct = hdcs->exp.cto + hdcs->psmp + (HDCS_ADC_START_SIG_DUR + 2);
cp = hdcs->exp.cto + (hdcs->w * ct / 2);
rp = hdcs->exp.rs + cp;
cycles = rp * rowexp;
*val = cycles / HDCS_CLK_FREQ_MHZ;
PDEBUG(D_V4L2, "Read exposure %d", *val);
return 0;
}
static int hdcs_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
{
struct sd *sd = (struct sd *) gspca_dev;
struct hdcs *hdcs = sd->sensor_priv;
int rowexp, srowexp;
int max_srowexp;
/* Column time period */
int ct;
/* Column processing period */
int cp;
/* Row processing period */
int rp;
/* Minimum number of column timing periods
within the column processing period */
int mnct;
int cycles, err;
u8 exp[4];
cycles = val * HDCS_CLK_FREQ_MHZ;
ct = hdcs->exp.cto + hdcs->psmp + (HDCS_ADC_START_SIG_DUR + 2);
cp = hdcs->exp.cto + (hdcs->w * ct / 2);
/* the cycles one row takes */
rp = hdcs->exp.rs + cp;
rowexp = cycles / rp;
/* the remaining cycles */
cycles -= rowexp * rp;
/* calculate sub-row exposure */
if (IS_1020(sd)) {
/* see HDCS-1020 datasheet 3.5.6.4, p. 63 */
srowexp = hdcs->w - (cycles + hdcs->exp.er + 13) / ct;
mnct = (hdcs->exp.er + 12 + ct - 1) / ct;
max_srowexp = hdcs->w - mnct;
} else {
/* see HDCS-1000 datasheet 3.4.5.5, p. 61 */
srowexp = cp - hdcs->exp.er - 6 - cycles;
mnct = (hdcs->exp.er + 5 + ct - 1) / ct;
max_srowexp = cp - mnct * ct - 1;
}
if (srowexp < 0)
srowexp = 0;
else if (srowexp > max_srowexp)
srowexp = max_srowexp;
if (IS_1020(sd)) {
exp[0] = rowexp & 0xff;
exp[1] = rowexp >> 8;
exp[2] = (srowexp >> 2) & 0xff;
/* this clears exposure error flag */
exp[3] = 0x1;
err = hdcs_reg_write_seq(sd, HDCS_ROWEXPL, exp, 4);
} else {
exp[0] = rowexp & 0xff;
exp[1] = rowexp >> 8;
exp[2] = srowexp & 0xff;
exp[3] = srowexp >> 8;
err = hdcs_reg_write_seq(sd, HDCS_ROWEXPL, exp, 4);
if (err < 0)
return err;
/* clear exposure error flag */
err = stv06xx_write_sensor(sd,
HDCS_STATUS, BIT(4));
}
PDEBUG(D_V4L2, "Writing exposure %d, rowexp %d, srowexp %d",
val, rowexp, srowexp);
return err;
}
static int hdcs_set_gains(struct sd *sd, u8 r, u8 g, u8 b)
{
u8 gains[4];
/* the voltage gain Av = (1 + 19 * val / 127) * (1 + bit7) */
if (r > 127)
r = 0x80 | (r / 2);
if (g > 127)
g = 0x80 | (g / 2);
if (b > 127)
b = 0x80 | (b / 2);
gains[0] = g;
gains[1] = r;
gains[2] = b;
gains[3] = g;
return hdcs_reg_write_seq(sd, HDCS_ERECPGA, gains, 4);
}
static int hdcs_get_gain(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
int err;
u16 data;
err = stv06xx_read_sensor(sd, HDCS_ERECPGA, &data);
/* Bit 7 doubles the gain */
if (data & 0x80)
*val = (data & 0x7f) * 2;
else
*val = data;
PDEBUG(D_V4L2, "Read gain %d", *val);
return err;
}
static int hdcs_set_gain(struct gspca_dev *gspca_dev, __s32 val)
{
PDEBUG(D_V4L2, "Writing gain %d", val);
return hdcs_set_gains((struct sd *) gspca_dev,
val & 0xff, val & 0xff, val & 0xff);
}
static int hdcs_set_size(struct sd *sd,
unsigned int width, unsigned int height)
{
struct hdcs *hdcs = sd->sensor_priv;
u8 win[4];
unsigned int x, y;
int err;
/* must be multiple of 4 */
width = (width + 3) & ~0x3;
height = (height + 3) & ~0x3;
if (width > hdcs->array.width)
width = hdcs->array.width;
if (IS_1020(sd)) {
/* the borders are also invalid */
if (height + 2 * hdcs->array.border + HDCS_1020_BOTTOM_Y_SKIP
> hdcs->array.height)
height = hdcs->array.height - 2 * hdcs->array.border -
HDCS_1020_BOTTOM_Y_SKIP;
y = (hdcs->array.height - HDCS_1020_BOTTOM_Y_SKIP - height) / 2
+ hdcs->array.top;
} else if (height > hdcs->array.height) {
height = hdcs->array.height;
y = hdcs->array.top + (hdcs->array.height - height) / 2;
}
x = hdcs->array.left + (hdcs->array.width - width) / 2;
win[0] = y / 4;
win[1] = x / 4;
win[2] = (y + height) / 4 - 1;
win[3] = (x + width) / 4 - 1;
err = hdcs_reg_write_seq(sd, HDCS_FWROW, win, 4);
if (err < 0)
return err;
/* Update the current width and height */
hdcs->w = width;
hdcs->h = height;
return err;
}
static int hdcs_probe_1x00(struct sd *sd)
{
struct hdcs *hdcs;
u16 sensor;
int ret;
ret = stv06xx_read_sensor(sd, HDCS_IDENT, &sensor);
if (ret < 0 || sensor != 0x08)
return -ENODEV;
info("HDCS-1000/1100 sensor detected");
sd->gspca_dev.cam.cam_mode = stv06xx_sensor_hdcs1x00.modes;
sd->gspca_dev.cam.nmodes = stv06xx_sensor_hdcs1x00.nmodes;
sd->desc.ctrls = stv06xx_sensor_hdcs1x00.ctrls;
sd->desc.nctrls = stv06xx_sensor_hdcs1x00.nctrls;
hdcs = kmalloc(sizeof(struct hdcs), GFP_KERNEL);
if (!hdcs)
return -ENOMEM;
hdcs->array.left = 8;
hdcs->array.top = 8;
hdcs->array.width = HDCS_1X00_DEF_WIDTH;
hdcs->array.height = HDCS_1X00_DEF_HEIGHT;
hdcs->array.border = 4;
hdcs->exp.cto = 4;
hdcs->exp.cpo = 2;
hdcs->exp.rs = 186;
hdcs->exp.er = 100;
/*
* Frame rate on HDCS-1000 0x46D:0x840 depends on PSMP:
* 4 = doesn't work at all
* 5 = 7.8 fps,
* 6 = 6.9 fps,
* 8 = 6.3 fps,
* 10 = 5.5 fps,
* 15 = 4.4 fps,
* 31 = 2.8 fps
*
* Frame rate on HDCS-1000 0x46D:0x870 depends on PSMP:
* 15 = doesn't work at all
* 18 = doesn't work at all
* 19 = 7.3 fps
* 20 = 7.4 fps
* 21 = 7.4 fps
* 22 = 7.4 fps
* 24 = 6.3 fps
* 30 = 5.4 fps
*/
hdcs->psmp = IS_870(sd) ? 20 : 5;
sd->sensor_priv = hdcs;
return 0;
}
static int hdcs_probe_1020(struct sd *sd)
{
struct hdcs *hdcs;
u16 sensor;
int ret;
ret = stv06xx_read_sensor(sd, HDCS_IDENT, &sensor);
if (ret < 0 || sensor != 0x10)
return -ENODEV;
info("HDCS-1020 sensor detected");
sd->gspca_dev.cam.cam_mode = stv06xx_sensor_hdcs1020.modes;
sd->gspca_dev.cam.nmodes = stv06xx_sensor_hdcs1020.nmodes;
sd->desc.ctrls = stv06xx_sensor_hdcs1020.ctrls;
sd->desc.nctrls = stv06xx_sensor_hdcs1020.nctrls;
hdcs = kmalloc(sizeof(struct hdcs), GFP_KERNEL);
if (!hdcs)
return -ENOMEM;
/*
* From Andrey's test image: looks like HDCS-1020 upper-left
* visible pixel is at 24,8 (y maybe even smaller?) and lower-right
* visible pixel at 375,299 (x maybe even larger?)
*/
hdcs->array.left = 24;
hdcs->array.top = 4;
hdcs->array.width = HDCS_1020_DEF_WIDTH;
hdcs->array.height = 304;
hdcs->array.border = 4;
hdcs->psmp = 6;
hdcs->exp.cto = 3;
hdcs->exp.cpo = 3;
hdcs->exp.rs = 155;
hdcs->exp.er = 96;
sd->sensor_priv = hdcs;
return 0;
}
static int hdcs_start(struct sd *sd)
{
PDEBUG(D_STREAM, "Starting stream");
return hdcs_set_state(sd, HDCS_STATE_RUN);
}
static int hdcs_stop(struct sd *sd)
{
PDEBUG(D_STREAM, "Halting stream");
return hdcs_set_state(sd, HDCS_STATE_SLEEP);
}
static void hdcs_disconnect(struct sd *sd)
{
PDEBUG(D_PROBE, "Disconnecting the sensor");
kfree(sd->sensor_priv);
}
static int hdcs_init(struct sd *sd)
{
struct hdcs *hdcs = sd->sensor_priv;
int i, err = 0;
/* Set the STV0602AA in STV0600 emulation mode */
if (IS_870(sd))
stv06xx_write_bridge(sd, STV_STV0600_EMULATION, 1);
/* Execute the bridge init */
for (i = 0; i < ARRAY_SIZE(stv_bridge_init) && !err; i++) {
err = stv06xx_write_bridge(sd, stv_bridge_init[i][0],
stv_bridge_init[i][1]);
}
if (err < 0)
return err;
/* sensor soft reset */
hdcs_reset(sd);
/* Execute the sensor init */
for (i = 0; i < ARRAY_SIZE(stv_sensor_init) && !err; i++) {
err = stv06xx_write_sensor(sd, stv_sensor_init[i][0],
stv_sensor_init[i][1]);
}
if (err < 0)
return err;
/* Enable continous frame capture, bit 2: stop when frame complete */
err = stv06xx_write_sensor(sd, HDCS_REG_CONFIG(sd), BIT(3));
if (err < 0)
return err;
/* Set PGA sample duration
(was 0x7E for IS_870, but caused slow framerate with HDCS-1020) */
if (IS_1020(sd))
err = stv06xx_write_sensor(sd, HDCS_TCTRL,
(HDCS_ADC_START_SIG_DUR << 6) | hdcs->psmp);
else
err = stv06xx_write_sensor(sd, HDCS_TCTRL,
(HDCS_ADC_START_SIG_DUR << 5) | hdcs->psmp);
if (err < 0)
return err;
err = hdcs_set_gains(sd, HDCS_DEFAULT_GAIN, HDCS_DEFAULT_GAIN,
HDCS_DEFAULT_GAIN);
if (err < 0)
return err;
err = hdcs_set_exposure(&sd->gspca_dev, HDCS_DEFAULT_EXPOSURE);
if (err < 0)
return err;
err = hdcs_set_size(sd, hdcs->array.width, hdcs->array.height);
return err;
}
static int hdcs_dump(struct sd *sd)
{
u16 reg, val;
info("Dumping sensor registers:");
for (reg = HDCS_IDENT; reg <= HDCS_ROWEXPH; reg++) {
stv06xx_read_sensor(sd, reg, &val);
info("reg 0x%02x = 0x%02x", reg, val);
}
return 0;
}
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
* Copyright (c) 2008 Chia-I Wu
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
#ifndef STV06XX_HDCS_H_
#define STV06XX_HDCS_H_
#include "stv06xx_sensor.h"
#define HDCS_REG_CONFIG(sd) (IS_1020(sd) ? HDCS20_CONFIG : HDCS00_CONFIG)
#define HDCS_REG_CONTROL(sd) (IS_1020(sd) ? HDCS20_CONTROL : HDCS00_CONTROL)
#define HDCS_1X00_DEF_WIDTH 360
#define HDCS_1X00_DEF_HEIGHT 296
#define HDCS_1020_DEF_WIDTH 352
#define HDCS_1020_DEF_HEIGHT 292
#define HDCS_1020_BOTTOM_Y_SKIP 4
#define HDCS_CLK_FREQ_MHZ 25
#define HDCS_ADC_START_SIG_DUR 3
/* LSB bit of I2C or register address signifies write (0) or read (1) */
/* I2C Registers common for both HDCS-1000/1100 and HDCS-1020 */
/* Identifications Register */
#define HDCS_IDENT (0x00 << 1)
/* Status Register */
#define HDCS_STATUS (0x01 << 1)
/* Interrupt Mask Register */
#define HDCS_IMASK (0x02 << 1)
/* Pad Control Register */
#define HDCS_PCTRL (0x03 << 1)
/* Pad Drive Control Register */
#define HDCS_PDRV (0x04 << 1)
/* Interface Control Register */
#define HDCS_ICTRL (0x05 << 1)
/* Interface Timing Register */
#define HDCS_ITMG (0x06 << 1)
/* Baud Fraction Register */
#define HDCS_BFRAC (0x07 << 1)
/* Baud Rate Register */
#define HDCS_BRATE (0x08 << 1)
/* ADC Control Register */
#define HDCS_ADCCTRL (0x09 << 1)
/* First Window Row Register */
#define HDCS_FWROW (0x0a << 1)
/* First Window Column Register */
#define HDCS_FWCOL (0x0b << 1)
/* Last Window Row Register */
#define HDCS_LWROW (0x0c << 1)
/* Last Window Column Register */
#define HDCS_LWCOL (0x0d << 1)
/* Timing Control Register */
#define HDCS_TCTRL (0x0e << 1)
/* PGA Gain Register: Even Row, Even Column */
#define HDCS_ERECPGA (0x0f << 1)
/* PGA Gain Register: Even Row, Odd Column */
#define HDCS_EROCPGA (0x10 << 1)
/* PGA Gain Register: Odd Row, Even Column */
#define HDCS_ORECPGA (0x11 << 1)
/* PGA Gain Register: Odd Row, Odd Column */
#define HDCS_OROCPGA (0x12 << 1)
/* Row Exposure Low Register */
#define HDCS_ROWEXPL (0x13 << 1)
/* Row Exposure High Register */
#define HDCS_ROWEXPH (0x14 << 1)
/* I2C Registers only for HDCS-1000/1100 */
/* Sub-Row Exposure Low Register */
#define HDCS00_SROWEXPL (0x15 << 1)
/* Sub-Row Exposure High Register */
#define HDCS00_SROWEXPH (0x16 << 1)
/* Configuration Register */
#define HDCS00_CONFIG (0x17 << 1)
/* Control Register */
#define HDCS00_CONTROL (0x18 << 1)
/* I2C Registers only for HDCS-1020 */
/* Sub-Row Exposure Register */
#define HDCS20_SROWEXP (0x15 << 1)
/* Error Control Register */
#define HDCS20_ERROR (0x16 << 1)
/* Interface Timing 2 Register */
#define HDCS20_ITMG2 (0x17 << 1)
/* Interface Control 2 Register */
#define HDCS20_ICTRL2 (0x18 << 1)
/* Horizontal Blank Register */
#define HDCS20_HBLANK (0x19 << 1)
/* Vertical Blank Register */
#define HDCS20_VBLANK (0x1a << 1)
/* Configuration Register */
#define HDCS20_CONFIG (0x1b << 1)
/* Control Register */
#define HDCS20_CONTROL (0x1c << 1)
#define HDCS_RUN_ENABLE (1 << 2)
#define HDCS_SLEEP_MODE (1 << 1)
#define HDCS_DEFAULT_EXPOSURE 5000
#define HDCS_DEFAULT_GAIN 128
static int hdcs_probe_1x00(struct sd *sd);
static int hdcs_probe_1020(struct sd *sd);
static int hdcs_start(struct sd *sd);
static int hdcs_init(struct sd *sd);
static int hdcs_stop(struct sd *sd);
static int hdcs_dump(struct sd *sd);
static void hdcs_disconnect(struct sd *sd);
static int hdcs_get_exposure(struct gspca_dev *gspca_dev, __s32 *val);
static int hdcs_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
static int hdcs_set_gain(struct gspca_dev *gspca_dev, __s32 val);
static int hdcs_get_gain(struct gspca_dev *gspca_dev, __s32 *val);
const struct stv06xx_sensor stv06xx_sensor_hdcs1x00 = {
.name = "HP HDCS-1000/1100",
.i2c_flush = 0,
.i2c_addr = (0x55 << 1),
.i2c_len = 1,
.init = hdcs_init,
.probe = hdcs_probe_1x00,
.start = hdcs_start,
.stop = hdcs_stop,
.disconnect = hdcs_disconnect,
.dump = hdcs_dump,
.nctrls = 2,
.ctrls = {
{
{
.id = V4L2_CID_EXPOSURE,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "exposure",
.minimum = 0x00,
.maximum = 0xffff,
.step = 0x1,
.default_value = HDCS_DEFAULT_EXPOSURE,
.flags = V4L2_CTRL_FLAG_SLIDER
},
.set = hdcs_set_exposure,
.get = hdcs_get_exposure
},
{
{
.id = V4L2_CID_GAIN,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "gain",
.minimum = 0x00,
.maximum = 0xff,
.step = 0x1,
.default_value = HDCS_DEFAULT_GAIN,
.flags = V4L2_CTRL_FLAG_SLIDER
},
.set = hdcs_set_gain,
.get = hdcs_get_gain
}
},
.nmodes = 1,
.modes = {
{
HDCS_1X00_DEF_WIDTH,
HDCS_1X00_DEF_HEIGHT,
V4L2_PIX_FMT_SBGGR8,
V4L2_FIELD_NONE,
.sizeimage =
HDCS_1X00_DEF_WIDTH * HDCS_1X00_DEF_HEIGHT,
.bytesperline = HDCS_1X00_DEF_WIDTH,
.colorspace = V4L2_COLORSPACE_SRGB,
.priv = 1
}
}
};
const struct stv06xx_sensor stv06xx_sensor_hdcs1020 = {
.name = "HDCS-1020",
.i2c_flush = 0,
.i2c_addr = (0x55 << 1),
.i2c_len = 1,
.nctrls = 0,
.ctrls = {},
.init = hdcs_init,
.probe = hdcs_probe_1020,
.start = hdcs_start,
.stop = hdcs_stop,
.dump = hdcs_dump,
.nmodes = 1,
.modes = {
{
HDCS_1020_DEF_WIDTH,
HDCS_1020_DEF_HEIGHT,
V4L2_PIX_FMT_SBGGR8,
V4L2_FIELD_NONE,
.sizeimage =
HDCS_1020_DEF_WIDTH * HDCS_1020_DEF_HEIGHT,
.bytesperline = HDCS_1020_DEF_WIDTH,
.colorspace = V4L2_COLORSPACE_SRGB,
.priv = 1
}
}
};
static const u16 stv_bridge_init[][2] = {
{STV_ISO_ENABLE, 0},
{STV_REG23, 0},
{STV_REG00, 0x1d},
{STV_REG01, 0xb5},
{STV_REG02, 0xa8},
{STV_REG03, 0x95},
{STV_REG04, 0x07},
{STV_SCAN_RATE, 0x20},
{STV_ISO_SIZE_L, 847},
{STV_Y_CTRL, 0x01},
{STV_X_CTRL, 0x0a}
};
static const u8 stv_sensor_init[][2] = {
/* Clear status (writing 1 will clear the corresponding status bit) */
{HDCS_STATUS, BIT(6) | BIT(5) | BIT(4) | BIT(3) | BIT(2) | BIT(1)},
/* Disable all interrupts */
{HDCS_IMASK, 0x00},
{HDCS_PCTRL, BIT(6) | BIT(5) | BIT(1) | BIT(0)},
{HDCS_PDRV, 0x00},
{HDCS_ICTRL, BIT(5)},
{HDCS_ITMG, BIT(4) | BIT(1)},
/* ADC output resolution to 10 bits */
{HDCS_ADCCTRL, 10}
};
#endif
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
/*
* The spec file for the PB-0100 suggests the following for best quality
* images after the sensor has been reset :
*
* PB_ADCGAINL = R60 = 0x03 (3 dec) : sets low reference of ADC
to produce good black level
* PB_PREADCTRL = R32 = 0x1400 (5120 dec) : Enables global gain changes
through R53
* PB_ADCMINGAIN = R52 = 0x10 (16 dec) : Sets the minimum gain for
auto-exposure
* PB_ADCGLOBALGAIN = R53 = 0x10 (16 dec) : Sets the global gain
* PB_EXPGAIN = R14 = 0x11 (17 dec) : Sets the auto-exposure value
* PB_UPDATEINT = R23 = 0x02 (2 dec) : Sets the speed on
auto-exposure routine
* PB_CFILLIN = R5 = 0x0E (14 dec) : Sets the frame rate
*/
#include "stv06xx_pb0100.h"
static int pb0100_probe(struct sd *sd)
{
u16 sensor;
int i, err;
s32 *sensor_settings;
err = stv06xx_read_sensor(sd, PB_IDENT, &sensor);
if (err < 0)
return -ENODEV;
if ((sensor >> 8) == 0x64) {
sensor_settings = kmalloc(
stv06xx_sensor_pb0100.nctrls * sizeof(s32),
GFP_KERNEL);
if (!sensor_settings)
return -ENOMEM;
info("Photobit pb0100 sensor detected");
sd->gspca_dev.cam.cam_mode = stv06xx_sensor_pb0100.modes;
sd->gspca_dev.cam.nmodes = stv06xx_sensor_pb0100.nmodes;
sd->desc.ctrls = stv06xx_sensor_pb0100.ctrls;
sd->desc.nctrls = stv06xx_sensor_pb0100.nctrls;
for (i = 0; i < stv06xx_sensor_pb0100.nctrls; i++)
sensor_settings[i] = stv06xx_sensor_pb0100.
ctrls[i].qctrl.default_value;
sd->sensor_priv = sensor_settings;
return 0;
}
return -ENODEV;
}
static int pb0100_start(struct sd *sd)
{
int err;
struct cam *cam = &sd->gspca_dev.cam;
s32 *sensor_settings = sd->sensor_priv;
u32 mode = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
/* Setup sensor window */
if (mode & PB0100_CROP_TO_VGA) {
stv06xx_write_sensor(sd, PB_RSTART, 30);
stv06xx_write_sensor(sd, PB_CSTART, 20);
stv06xx_write_sensor(sd, PB_RWSIZE, 240 - 1);
stv06xx_write_sensor(sd, PB_CWSIZE, 320 - 1);
} else {
stv06xx_write_sensor(sd, PB_RSTART, 8);
stv06xx_write_sensor(sd, PB_CSTART, 4);
stv06xx_write_sensor(sd, PB_RWSIZE, 288 - 1);
stv06xx_write_sensor(sd, PB_CWSIZE, 352 - 1);
}
if (mode & PB0100_SUBSAMPLE) {
stv06xx_write_bridge(sd, STV_Y_CTRL, 0x02); /* Wrong, FIXME */
stv06xx_write_bridge(sd, STV_X_CTRL, 0x06);
stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x10);
} else {
stv06xx_write_bridge(sd, STV_Y_CTRL, 0x01);
stv06xx_write_bridge(sd, STV_X_CTRL, 0x0a);
/* larger -> slower */
stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x20);
}
/* set_gain also sets red and blue balance */
pb0100_set_gain(&sd->gspca_dev, sensor_settings[GAIN_IDX]);
pb0100_set_exposure(&sd->gspca_dev, sensor_settings[EXPOSURE_IDX]);
pb0100_set_autogain_target(&sd->gspca_dev,
sensor_settings[AUTOGAIN_TARGET_IDX]);
pb0100_set_autogain(&sd->gspca_dev, sensor_settings[AUTOGAIN_IDX]);
err = stv06xx_write_sensor(sd, PB_CONTROL, BIT(5)|BIT(3)|BIT(1));
PDEBUG(D_STREAM, "Started stream, status: %d", err);
return (err < 0) ? err : 0;
}
static int pb0100_stop(struct sd *sd)
{
int err;
err = stv06xx_write_sensor(sd, PB_ABORTFRAME, 1);
if (err < 0)
goto out;
/* Set bit 1 to zero */
err = stv06xx_write_sensor(sd, PB_CONTROL, BIT(5)|BIT(3));
PDEBUG(D_STREAM, "Halting stream");
out:
return (err < 0) ? err : 0;
}
/* FIXME: Sort the init commands out and put them into tables,
this is only for getting the camera to work */
/* FIXME: No error handling for now,
add this once the init has been converted to proper tables */
static int pb0100_init(struct sd *sd)
{
stv06xx_write_bridge(sd, STV_REG00, 1);
stv06xx_write_bridge(sd, STV_SCAN_RATE, 0);
/* Reset sensor */
stv06xx_write_sensor(sd, PB_RESET, 1);
stv06xx_write_sensor(sd, PB_RESET, 0);
/* Disable chip */
stv06xx_write_sensor(sd, PB_CONTROL, BIT(5)|BIT(3));
/* Gain stuff...*/
stv06xx_write_sensor(sd, PB_PREADCTRL, BIT(12)|BIT(10)|BIT(6));
stv06xx_write_sensor(sd, PB_ADCGLOBALGAIN, 12);
/* Set up auto-exposure */
/* ADC VREF_HI new setting for a transition
from the Expose1 to the Expose2 setting */
stv06xx_write_sensor(sd, PB_R28, 12);
/* gain max for autoexposure */
stv06xx_write_sensor(sd, PB_ADCMAXGAIN, 180);
/* gain min for autoexposure */
stv06xx_write_sensor(sd, PB_ADCMINGAIN, 12);
/* Maximum frame integration time (programmed into R8)
allowed for auto-exposure routine */
stv06xx_write_sensor(sd, PB_R54, 3);
/* Minimum frame integration time (programmed into R8)
allowed for auto-exposure routine */
stv06xx_write_sensor(sd, PB_R55, 0);
stv06xx_write_sensor(sd, PB_UPDATEINT, 1);
/* R15 Expose0 (maximum that auto-exposure may use) */
stv06xx_write_sensor(sd, PB_R15, 800);
/* R17 Expose2 (minimum that auto-exposure may use) */
stv06xx_write_sensor(sd, PB_R17, 10);
stv06xx_write_sensor(sd, PB_EXPGAIN, 0);
/* 0x14 */
stv06xx_write_sensor(sd, PB_VOFFSET, 0);
/* 0x0D */
stv06xx_write_sensor(sd, PB_ADCGAINH, 11);
/* Set black level (important!) */
stv06xx_write_sensor(sd, PB_ADCGAINL, 0);
/* ??? */
stv06xx_write_bridge(sd, STV_REG00, 0x11);
stv06xx_write_bridge(sd, STV_REG03, 0x45);
stv06xx_write_bridge(sd, STV_REG04, 0x07);
/* ISO-Size (0x27b: 635... why? - HDCS uses 847) */
stv06xx_write_bridge(sd, STV_ISO_SIZE_L, 847);
/* Scan/timing for the sensor */
stv06xx_write_sensor(sd, PB_ROWSPEED, BIT(4)|BIT(3)|BIT(1));
stv06xx_write_sensor(sd, PB_CFILLIN, 14);
stv06xx_write_sensor(sd, PB_VBL, 0);
stv06xx_write_sensor(sd, PB_FINTTIME, 0);
stv06xx_write_sensor(sd, PB_RINTTIME, 123);
stv06xx_write_bridge(sd, STV_REG01, 0xc2);
stv06xx_write_bridge(sd, STV_REG02, 0xb0);
return 0;
}
static int pb0100_dump(struct sd *sd)
{
return 0;
}
static int pb0100_get_gain(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
*val = sensor_settings[GAIN_IDX];
return 0;
}
static int pb0100_set_gain(struct gspca_dev *gspca_dev, __s32 val)
{
int err;
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
if (sensor_settings[AUTOGAIN_IDX])
return -EBUSY;
sensor_settings[GAIN_IDX] = val;
err = stv06xx_write_sensor(sd, PB_G1GAIN, val);
if (!err)
err = stv06xx_write_sensor(sd, PB_G2GAIN, val);
PDEBUG(D_V4L2, "Set green gain to %d, status: %d", val, err);
if (!err)
err = pb0100_set_red_balance(gspca_dev,
sensor_settings[RED_BALANCE_IDX]);
if (!err)
err = pb0100_set_blue_balance(gspca_dev,
sensor_settings[BLUE_BALANCE_IDX]);
return err;
}
static int pb0100_get_red_balance(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
*val = sensor_settings[RED_BALANCE_IDX];
return 0;
}
static int pb0100_set_red_balance(struct gspca_dev *gspca_dev, __s32 val)
{
int err;
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
if (sensor_settings[AUTOGAIN_IDX])
return -EBUSY;
sensor_settings[RED_BALANCE_IDX] = val;
val += sensor_settings[GAIN_IDX];
if (val < 0)
val = 0;
else if (val > 255)
val = 255;
err = stv06xx_write_sensor(sd, PB_RGAIN, val);
PDEBUG(D_V4L2, "Set red gain to %d, status: %d", val, err);
return err;
}
static int pb0100_get_blue_balance(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
*val = sensor_settings[BLUE_BALANCE_IDX];
return 0;
}
static int pb0100_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val)
{
int err;
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
if (sensor_settings[AUTOGAIN_IDX])
return -EBUSY;
sensor_settings[BLUE_BALANCE_IDX] = val;
val += sensor_settings[GAIN_IDX];
if (val < 0)
val = 0;
else if (val > 255)
val = 255;
err = stv06xx_write_sensor(sd, PB_BGAIN, val);
PDEBUG(D_V4L2, "Set blue gain to %d, status: %d", val, err);
return err;
}
static int pb0100_get_exposure(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
*val = sensor_settings[EXPOSURE_IDX];
return 0;
}
static int pb0100_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
{
int err;
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
if (sensor_settings[AUTOGAIN_IDX])
return -EBUSY;
sensor_settings[EXPOSURE_IDX] = val;
err = stv06xx_write_sensor(sd, PB_RINTTIME, val);
PDEBUG(D_V4L2, "Set exposure to %d, status: %d", val, err);
return err;
}
static int pb0100_get_autogain(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
*val = sensor_settings[AUTOGAIN_IDX];
return 0;
}
static int pb0100_set_autogain(struct gspca_dev *gspca_dev, __s32 val)
{
int err;
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
sensor_settings[AUTOGAIN_IDX] = val;
if (sensor_settings[AUTOGAIN_IDX]) {
if (sensor_settings[NATURAL_IDX])
val = BIT(6)|BIT(4)|BIT(0);
else
val = BIT(4)|BIT(0);
} else
val = 0;
err = stv06xx_write_sensor(sd, PB_EXPGAIN, val);
PDEBUG(D_V4L2, "Set autogain to %d (natural: %d), status: %d",
sensor_settings[AUTOGAIN_IDX], sensor_settings[NATURAL_IDX],
err);
return err;
}
static int pb0100_get_autogain_target(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
*val = sensor_settings[AUTOGAIN_TARGET_IDX];
return 0;
}
static int pb0100_set_autogain_target(struct gspca_dev *gspca_dev, __s32 val)
{
int err, totalpixels, brightpixels, darkpixels;
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
sensor_settings[AUTOGAIN_TARGET_IDX] = val;
/* Number of pixels counted by the sensor when subsampling the pixels.
* Slightly larger than the real value to avoid oscillation */
totalpixels = gspca_dev->width * gspca_dev->height;
totalpixels = totalpixels/(8*8) + totalpixels/(64*64);
brightpixels = (totalpixels * val) >> 8;
darkpixels = totalpixels - brightpixels;
err = stv06xx_write_sensor(sd, PB_R21, brightpixels);
if (!err)
err = stv06xx_write_sensor(sd, PB_R22, darkpixels);
PDEBUG(D_V4L2, "Set autogain target to %d, status: %d", val, err);
return err;
}
static int pb0100_get_natural(struct gspca_dev *gspca_dev, __s32 *val)
{
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
*val = sensor_settings[NATURAL_IDX];
return 0;
}
static int pb0100_set_natural(struct gspca_dev *gspca_dev, __s32 val)
{
struct sd *sd = (struct sd *) gspca_dev;
s32 *sensor_settings = sd->sensor_priv;
sensor_settings[NATURAL_IDX] = val;
return pb0100_set_autogain(gspca_dev, sensor_settings[AUTOGAIN_IDX]);
}
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
#ifndef STV06XX_PB0100_H_
#define STV06XX_PB0100_H_
#include "stv06xx_sensor.h"
/* mode priv field flags */
#define PB0100_CROP_TO_VGA 0x01
#define PB0100_SUBSAMPLE 0x02
/* I2C Registers */
#define PB_IDENT 0x00 /* Chip Version */
#define PB_RSTART 0x01 /* Row Window Start */
#define PB_CSTART 0x02 /* Column Window Start */
#define PB_RWSIZE 0x03 /* Row Window Size */
#define PB_CWSIZE 0x04 /* Column Window Size */
#define PB_CFILLIN 0x05 /* Column Fill-In */
#define PB_VBL 0x06 /* Vertical Blank Count */
#define PB_CONTROL 0x07 /* Control Mode */
#define PB_FINTTIME 0x08 /* Integration Time/Frame Unit Count */
#define PB_RINTTIME 0x09 /* Integration Time/Row Unit Count */
#define PB_ROWSPEED 0x0a /* Row Speed Control */
#define PB_ABORTFRAME 0x0b /* Abort Frame */
#define PB_R12 0x0c /* Reserved */
#define PB_RESET 0x0d /* Reset */
#define PB_EXPGAIN 0x0e /* Exposure Gain Command */
#define PB_R15 0x0f /* Expose0 */
#define PB_R16 0x10 /* Expose1 */
#define PB_R17 0x11 /* Expose2 */
#define PB_R18 0x12 /* Low0_DAC */
#define PB_R19 0x13 /* Low1_DAC */
#define PB_R20 0x14 /* Low2_DAC */
#define PB_R21 0x15 /* Threshold11 */
#define PB_R22 0x16 /* Threshold0x */
#define PB_UPDATEINT 0x17 /* Update Interval */
#define PB_R24 0x18 /* High_DAC */
#define PB_R25 0x19 /* Trans0H */
#define PB_R26 0x1a /* Trans1L */
#define PB_R27 0x1b /* Trans1H */
#define PB_R28 0x1c /* Trans2L */
#define PB_R29 0x1d /* Reserved */
#define PB_R30 0x1e /* Reserved */
#define PB_R31 0x1f /* Wait to Read */
#define PB_PREADCTRL 0x20 /* Pixel Read Control Mode */
#define PB_R33 0x21 /* IREF_VLN */
#define PB_R34 0x22 /* IREF_VLP */
#define PB_R35 0x23 /* IREF_VLN_INTEG */
#define PB_R36 0x24 /* IREF_MASTER */
#define PB_R37 0x25 /* IDACP */
#define PB_R38 0x26 /* IDACN */
#define PB_R39 0x27 /* DAC_Control_Reg */
#define PB_R40 0x28 /* VCL */
#define PB_R41 0x29 /* IREF_VLN_ADCIN */
#define PB_R42 0x2a /* Reserved */
#define PB_G1GAIN 0x2b /* Green 1 Gain */
#define PB_BGAIN 0x2c /* Blue Gain */
#define PB_RGAIN 0x2d /* Red Gain */
#define PB_G2GAIN 0x2e /* Green 2 Gain */
#define PB_R47 0x2f /* Dark Row Address */
#define PB_R48 0x30 /* Dark Row Options */
#define PB_R49 0x31 /* Reserved */
#define PB_R50 0x32 /* Image Test Data */
#define PB_ADCMAXGAIN 0x33 /* Maximum Gain */
#define PB_ADCMINGAIN 0x34 /* Minimum Gain */
#define PB_ADCGLOBALGAIN 0x35 /* Global Gain */
#define PB_R54 0x36 /* Maximum Frame */
#define PB_R55 0x37 /* Minimum Frame */
#define PB_R56 0x38 /* Reserved */
#define PB_VOFFSET 0x39 /* VOFFSET */
#define PB_R58 0x3a /* Snap-Shot Sequence Trigger */
#define PB_ADCGAINH 0x3b /* VREF_HI */
#define PB_ADCGAINL 0x3c /* VREF_LO */
#define PB_R61 0x3d /* Reserved */
#define PB_R62 0x3e /* Reserved */
#define PB_R63 0x3f /* Reserved */
#define PB_R64 0x40 /* Red/Blue Gain */
#define PB_R65 0x41 /* Green 2/Green 1 Gain */
#define PB_R66 0x42 /* VREF_HI/LO */
#define PB_R67 0x43 /* Integration Time/Row Unit Count */
#define PB_R240 0xf0 /* ADC Test */
#define PB_R241 0xf1 /* Chip Enable */
#define PB_R242 0xf2 /* Reserved */
static int pb0100_probe(struct sd *sd);
static int pb0100_start(struct sd *sd);
static int pb0100_init(struct sd *sd);
static int pb0100_stop(struct sd *sd);
static int pb0100_dump(struct sd *sd);
/* V4L2 controls supported by the driver */
static int pb0100_get_gain(struct gspca_dev *gspca_dev, __s32 *val);
static int pb0100_set_gain(struct gspca_dev *gspca_dev, __s32 val);
static int pb0100_get_red_balance(struct gspca_dev *gspca_dev, __s32 *val);
static int pb0100_set_red_balance(struct gspca_dev *gspca_dev, __s32 val);
static int pb0100_get_blue_balance(struct gspca_dev *gspca_dev, __s32 *val);
static int pb0100_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val);
static int pb0100_get_exposure(struct gspca_dev *gspca_dev, __s32 *val);
static int pb0100_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
static int pb0100_get_autogain(struct gspca_dev *gspca_dev, __s32 *val);
static int pb0100_set_autogain(struct gspca_dev *gspca_dev, __s32 val);
static int pb0100_get_autogain_target(struct gspca_dev *gspca_dev, __s32 *val);
static int pb0100_set_autogain_target(struct gspca_dev *gspca_dev, __s32 val);
static int pb0100_get_natural(struct gspca_dev *gspca_dev, __s32 *val);
static int pb0100_set_natural(struct gspca_dev *gspca_dev, __s32 val);
const struct stv06xx_sensor stv06xx_sensor_pb0100 = {
.name = "PB-0100",
.i2c_flush = 1,
.i2c_addr = 0xba,
.i2c_len = 2,
.nctrls = 7,
.ctrls = {
#define GAIN_IDX 0
{
{
.id = V4L2_CID_GAIN,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Gain",
.minimum = 0,
.maximum = 255,
.step = 1,
.default_value = 128
},
.set = pb0100_set_gain,
.get = pb0100_get_gain
},
#define RED_BALANCE_IDX 1
{
{
.id = V4L2_CID_RED_BALANCE,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Red Balance",
.minimum = -255,
.maximum = 255,
.step = 1,
.default_value = 0
},
.set = pb0100_set_red_balance,
.get = pb0100_get_red_balance
},
#define BLUE_BALANCE_IDX 2
{
{
.id = V4L2_CID_BLUE_BALANCE,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Blue Balance",
.minimum = -255,
.maximum = 255,
.step = 1,
.default_value = 0
},
.set = pb0100_set_blue_balance,
.get = pb0100_get_blue_balance
},
#define EXPOSURE_IDX 3
{
{
.id = V4L2_CID_EXPOSURE,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Exposure",
.minimum = 0,
.maximum = 511,
.step = 1,
.default_value = 12
},
.set = pb0100_set_exposure,
.get = pb0100_get_exposure
},
#define AUTOGAIN_IDX 4
{
{
.id = V4L2_CID_AUTOGAIN,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Automatic Gain and Exposure",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 1
},
.set = pb0100_set_autogain,
.get = pb0100_get_autogain
},
#define AUTOGAIN_TARGET_IDX 5
{
{
.id = V4L2_CTRL_CLASS_USER + 0x1000,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Automatic Gain Target",
.minimum = 0,
.maximum = 255,
.step = 1,
.default_value = 128
},
.set = pb0100_set_autogain_target,
.get = pb0100_get_autogain_target
},
#define NATURAL_IDX 6
{
{
.id = V4L2_CTRL_CLASS_USER + 0x1001,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Natural Light Source",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 1
},
.set = pb0100_set_natural,
.get = pb0100_get_natural
},
},
.init = pb0100_init,
.probe = pb0100_probe,
.start = pb0100_start,
.stop = pb0100_stop,
.dump = pb0100_dump,
.nmodes = 2,
.modes = {
/* low res / subsample modes disabled as they are only half res horizontal,
halving the vertical resolution does not seem to work */
{
320,
240,
V4L2_PIX_FMT_SGRBG8,
V4L2_FIELD_NONE,
.sizeimage = 320 * 240,
.bytesperline = 320,
.colorspace = V4L2_COLORSPACE_SRGB,
.priv = PB0100_CROP_TO_VGA
},
{
352,
288,
V4L2_PIX_FMT_SGRBG8,
V4L2_FIELD_NONE,
.sizeimage = 352 * 288,
.bytesperline = 352,
.colorspace = V4L2_COLORSPACE_SRGB,
.priv = 0
},
}
};
#endif
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
#ifndef STV06XX_SENSOR_H_
#define STV06XX_SENSOR_H_
#include "stv06xx.h"
#define IS_850(sd) ((sd)->gspca_dev.dev->descriptor.idProduct == 0x850)
#define IS_870(sd) ((sd)->gspca_dev.dev->descriptor.idProduct == 0x870)
#define IS_1020(sd) ((sd)->sensor == &stv06xx_sensor_hdcs1020)
extern const struct stv06xx_sensor stv06xx_sensor_vv6410;
extern const struct stv06xx_sensor stv06xx_sensor_hdcs1x00;
extern const struct stv06xx_sensor stv06xx_sensor_hdcs1020;
extern const struct stv06xx_sensor stv06xx_sensor_pb0100;
#define STV06XX_MAX_CTRLS (V4L2_CID_LASTP1 - V4L2_CID_BASE + 10)
struct stv06xx_sensor {
/* Defines the name of a sensor */
char name[32];
/* Sensor i2c address */
u8 i2c_addr;
/* Flush value*/
u8 i2c_flush;
/* length of an i2c word */
u8 i2c_len;
/* Probes if the sensor is connected */
int (*probe)(struct sd *sd);
/* Performs a initialization sequence */
int (*init)(struct sd *sd);
/* Executed at device disconnect */
void (*disconnect)(struct sd *sd);
/* Reads a sensor register */
int (*read_sensor)(struct sd *sd, const u8 address,
u8 *i2c_data, const u8 len);
/* Writes to a sensor register */
int (*write_sensor)(struct sd *sd, const u8 address,
u8 *i2c_data, const u8 len);
/* Instructs the sensor to start streaming */
int (*start)(struct sd *sd);
/* Instructs the sensor to stop streaming */
int (*stop)(struct sd *sd);
/* Instructs the sensor to dump all its contents */
int (*dump)(struct sd *sd);
int nctrls;
struct ctrl ctrls[STV06XX_MAX_CTRLS];
char nmodes;
struct v4l2_pix_format modes[];
};
#endif
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
#include "stv06xx_vv6410.h"
static int vv6410_probe(struct sd *sd)
{
u16 data;
int err;
err = stv06xx_read_sensor(sd, VV6410_DEVICEH, &data);
if (err < 0)
return -ENODEV;
if (data == 0x19) {
info("vv6410 sensor detected");
sd->gspca_dev.cam.cam_mode = stv06xx_sensor_vv6410.modes;
sd->gspca_dev.cam.nmodes = stv06xx_sensor_vv6410.nmodes;
sd->desc.ctrls = stv06xx_sensor_vv6410.ctrls;
sd->desc.nctrls = stv06xx_sensor_vv6410.nctrls;
return 0;
}
return -ENODEV;
}
static int vv6410_init(struct sd *sd)
{
int err = 0, i;
for (i = 0; i < ARRAY_SIZE(stv_bridge_init); i++) {
/* if NULL then len contains single value */
if (stv_bridge_init[i].data == NULL) {
err = stv06xx_write_bridge(sd,
stv_bridge_init[i].start,
stv_bridge_init[i].len);
} else {
int j;
for (j = 0; j < stv_bridge_init[i].len; j++)
err = stv06xx_write_bridge(sd,
stv_bridge_init[i].start + j,
stv_bridge_init[i].data[j]);
}
}
if (err < 0)
return err;
err = stv06xx_write_sensor_bytes(sd, (u8 *) vv6410_sensor_init,
ARRAY_SIZE(vv6410_sensor_init));
return (err < 0) ? err : 0;
}
static int vv6410_start(struct sd *sd)
{
int err;
struct cam *cam = &sd->gspca_dev.cam;
u32 priv = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
if (priv & VV6410_CROP_TO_QVGA) {
PDEBUG(D_CONF, "Cropping to QVGA");
stv06xx_write_sensor(sd, VV6410_XENDH, 320 - 1);
stv06xx_write_sensor(sd, VV6410_YENDH, 240 - 1);
} else {
stv06xx_write_sensor(sd, VV6410_XENDH, 360 - 1);
stv06xx_write_sensor(sd, VV6410_YENDH, 294 - 1);
}
if (priv & VV6410_SUBSAMPLE) {
PDEBUG(D_CONF, "Enabling subsampling");
stv06xx_write_bridge(sd, STV_Y_CTRL, 0x02);
stv06xx_write_bridge(sd, STV_X_CTRL, 0x06);
stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x10);
} else {
stv06xx_write_bridge(sd, STV_Y_CTRL, 0x01);
stv06xx_write_bridge(sd, STV_X_CTRL, 0x0a);
stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x20);
}
/* Turn on LED */
err = stv06xx_write_bridge(sd, STV_LED_CTRL, LED_ON);
if (err < 0)
return err;
err = stv06xx_write_sensor(sd, VV6410_SETUP0, 0);
if (err < 0)
return err;
PDEBUG(D_STREAM, "Starting stream");
return 0;
}
static int vv6410_stop(struct sd *sd)
{
int err;
/* Turn off LED */
err = stv06xx_write_bridge(sd, STV_LED_CTRL, LED_OFF);
if (err < 0)
return err;
err = stv06xx_write_sensor(sd, VV6410_SETUP0, VV6410_LOW_POWER_MODE);
if (err < 0)
return err;
PDEBUG(D_STREAM, "Halting stream");
return (err < 0) ? err : 0;
}
static int vv6410_dump(struct sd *sd)
{
u8 i;
int err = 0;
info("Dumping all vv6410 sensor registers");
for (i = 0; i < 0xff && !err; i++) {
u16 data;
err = stv06xx_read_sensor(sd, i, &data);
info("Register 0x%x contained 0x%x", i, data);
}
return (err < 0) ? err : 0;
}
static int vv6410_get_hflip(struct gspca_dev *gspca_dev, __s32 *val)
{
int err;
u16 i2c_data;
struct sd *sd = (struct sd *) gspca_dev;
err = stv06xx_read_sensor(sd, VV6410_DATAFORMAT, &i2c_data);
*val = (i2c_data & VV6410_HFLIP) ? 1 : 0;
PDEBUG(D_V4L2, "Read horizontal flip %d", *val);
return (err < 0) ? err : 0;
}
static int vv6410_set_hflip(struct gspca_dev *gspca_dev, __s32 val)
{
int err;
u16 i2c_data;
struct sd *sd = (struct sd *) gspca_dev;
err = stv06xx_read_sensor(sd, VV6410_DATAFORMAT, &i2c_data);
if (err < 0)
return err;
if (val)
i2c_data |= VV6410_HFLIP;
else
i2c_data &= ~VV6410_HFLIP;
PDEBUG(D_V4L2, "Set horizontal flip to %d", val);
err = stv06xx_write_sensor(sd, VV6410_DATAFORMAT, i2c_data);
return (err < 0) ? err : 0;
}
static int vv6410_get_vflip(struct gspca_dev *gspca_dev, __s32 *val)
{
int err;
u16 i2c_data;
struct sd *sd = (struct sd *) gspca_dev;
err = stv06xx_read_sensor(sd, VV6410_DATAFORMAT, &i2c_data);
*val = (i2c_data & VV6410_VFLIP) ? 1 : 0;
PDEBUG(D_V4L2, "Read vertical flip %d", *val);
return (err < 0) ? err : 0;
}
static int vv6410_set_vflip(struct gspca_dev *gspca_dev, __s32 val)
{
int err;
u16 i2c_data;
struct sd *sd = (struct sd *) gspca_dev;
err = stv06xx_read_sensor(sd, VV6410_DATAFORMAT, &i2c_data);
if (err < 0)
return err;
if (val)
i2c_data |= VV6410_VFLIP;
else
i2c_data &= ~VV6410_VFLIP;
PDEBUG(D_V4L2, "Set vertical flip to %d", val);
err = stv06xx_write_sensor(sd, VV6410_DATAFORMAT, i2c_data);
return (err < 0) ? err : 0;
}
static int vv6410_get_analog_gain(struct gspca_dev *gspca_dev, __s32 *val)
{
int err;
u16 i2c_data;
struct sd *sd = (struct sd *) gspca_dev;
err = stv06xx_read_sensor(sd, VV6410_ANALOGGAIN, &i2c_data);
*val = i2c_data & 0xf;
PDEBUG(D_V4L2, "Read analog gain %d", *val);
return (err < 0) ? err : 0;
}
static int vv6410_set_analog_gain(struct gspca_dev *gspca_dev, __s32 val)
{
int err;
struct sd *sd = (struct sd *) gspca_dev;
PDEBUG(D_V4L2, "Set analog gain to %d", val);
err = stv06xx_write_sensor(sd, VV6410_ANALOGGAIN, 0xf0 | (val & 0xf));
return (err < 0) ? err : 0;
}
/*
* Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
* Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
* Copyright (c) 2002, 2003 Tuukka Toivonen
* Copyright (c) 2008 Erik Andrén
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* P/N 861037: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0010: Sensor HDCS1000 ASIC STV0600
* P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express
* P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam
* P/N 861075-0040: Sensor HDCS1000 ASIC
* P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB
* P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web
*/
#ifndef STV06XX_VV6410_H_
#define STV06XX_VV6410_H_
#include "stv06xx_sensor.h"
#define VV6410_COLS 416
#define VV6410_ROWS 320
/* Status registers */
/* Chip identification number including revision indicator */
#define VV6410_DEVICEH 0x00
#define VV6410_DEVICEL 0x01
/* User can determine whether timed I2C data
has been consumed by interrogating flag states */
#define VV6410_STATUS0 0x02
/* Current line counter value */
#define VV6410_LINECOUNTH 0x03
#define VV6410_LINECOUNTL 0x04
/* End x coordinate of image size */
#define VV6410_XENDH 0x05
#define VV6410_XENDL 0x06
/* End y coordinate of image size */
#define VV6410_YENDH 0x07
#define VV6410_YENDL 0x08
/* This is the average pixel value returned from the
dark line offset cancellation algorithm */
#define VV6410_DARKAVGH 0x09
#define VV6410_DARKAVGL 0x0a
/* This is the average pixel value returned from the
black line offset cancellation algorithm */
#define VV6410_BLACKAVGH 0x0b
#define VV6410_BLACKAVGL 0x0c
/* Flags to indicate whether the x or y image coordinates have been clipped */
#define VV6410_STATUS1 0x0d
/* Setup registers */
/* Low-power/sleep modes & video timing */
#define VV6410_SETUP0 0x10
/* Various parameters */
#define VV6410_SETUP1 0x11
/* Contains pixel counter reset value used by external sync */
#define VV6410_SYNCVALUE 0x12
/* Frame grabbing modes (FST, LST and QCK) */
#define VV6410_FGMODES 0x14
/* FST and QCK mapping modes. */
#define VV6410_PINMAPPING 0x15
/* Data resolution */
#define VV6410_DATAFORMAT 0x16
/* Output coding formats */
#define VV6410_OPFORMAT 0x17
/* Various mode select bits */
#define VV6410_MODESELECT 0x18
/* Exposure registers */
/* Fine exposure. */
#define VV6410_FINEH 0x20
#define VV6410_FINEL 0x21
/* Coarse exposure */
#define VV6410_COARSEH 0x22
#define VV6410_COARSEL 0x23
/* Analog gain setting */
#define VV6410_ANALOGGAIN 0x24
/* Clock division */
#define VV6410_CLKDIV 0x25
/* Dark line offset cancellation value */
#define VV6410_DARKOFFSETH 0x2c
#define VV6410_DARKOFFSETL 0x2d
/* Dark line offset cancellation enable */
#define VV6410_DARKOFFSETSETUP 0x2e
/* Video timing registers */
/* Line Length (Pixel Clocks) */
#define VV6410_LINELENGTHH 0x52
#define VV6410_LINELENGTHL 0x53
/* X-co-ordinate of top left corner of region of interest (x-offset) */
#define VV6410_XOFFSETH 0x57
#define VV6410_XOFFSETL 0x58
/* Y-coordinate of top left corner of region of interest (y-offset) */
#define VV6410_YOFFSETH 0x59
#define VV6410_YOFFSETL 0x5a
/* Field length (Lines) */
#define VV6410_FIELDLENGTHH 0x61
#define VV6410_FIELDLENGTHL 0x62
/* System registers */
/* Black offset cancellation default value */
#define VV6410_BLACKOFFSETH 0x70
#define VV6410_BLACKOFFSETL 0x71
/* Black offset cancellation setup */
#define VV6410_BLACKOFFSETSETUP 0x72
/* Analog Control Register 0 */
#define VV6410_CR0 0x75
/* Analog Control Register 1 */
#define VV6410_CR1 0x76
/* ADC Setup Register */
#define VV6410_AS0 0x77
/* Analog Test Register */
#define VV6410_AT0 0x78
/* Audio Amplifier Setup Register */
#define VV6410_AT1 0x79
#define VV6410_HFLIP (1 << 3)
#define VV6410_VFLIP (1 << 4)
#define VV6410_LOW_POWER_MODE (1 << 0)
#define VV6410_SOFT_RESET (1 << 2)
#define VV6410_PAL_25_FPS (0 << 3)
#define VV6410_CLK_DIV_2 (1 << 1)
#define VV6410_FINE_EXPOSURE 320
#define VV6410_COARSE_EXPOSURE 192
#define VV6410_DEFAULT_GAIN 5
#define VV6410_SUBSAMPLE 0x01
#define VV6410_CROP_TO_QVGA 0x02
static int vv6410_probe(struct sd *sd);
static int vv6410_start(struct sd *sd);
static int vv6410_init(struct sd *sd);
static int vv6410_stop(struct sd *sd);
static int vv6410_dump(struct sd *sd);
/* V4L2 controls supported by the driver */
static int vv6410_get_hflip(struct gspca_dev *gspca_dev, __s32 *val);
static int vv6410_set_hflip(struct gspca_dev *gspca_dev, __s32 val);
static int vv6410_get_vflip(struct gspca_dev *gspca_dev, __s32 *val);
static int vv6410_set_vflip(struct gspca_dev *gspca_dev, __s32 val);
static int vv6410_get_analog_gain(struct gspca_dev *gspca_dev, __s32 *val);
static int vv6410_set_analog_gain(struct gspca_dev *gspca_dev, __s32 val);
const struct stv06xx_sensor stv06xx_sensor_vv6410 = {
.name = "ST VV6410",
.i2c_flush = 5,
.i2c_addr = 0x20,
.i2c_len = 1,
.init = vv6410_init,
.probe = vv6410_probe,
.start = vv6410_start,
.stop = vv6410_stop,
.dump = vv6410_dump,
.nctrls = 3,
.ctrls = {
{
{
.id = V4L2_CID_HFLIP,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "horizontal flip",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 0
},
.set = vv6410_set_hflip,
.get = vv6410_get_hflip
}, {
{
.id = V4L2_CID_VFLIP,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "vertical flip",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 0
},
.set = vv6410_set_vflip,
.get = vv6410_get_vflip
}, {
{
.id = V4L2_CID_GAIN,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "analog gain",
.minimum = 0,
.maximum = 15,
.step = 1,
.default_value = 0
},
.set = vv6410_set_analog_gain,
.get = vv6410_get_analog_gain
}
},
.nmodes = 1,
.modes = {
{
356,
292,
V4L2_PIX_FMT_SGRBG8,
V4L2_FIELD_NONE,
.sizeimage =
356 * 292,
.bytesperline = 356,
.colorspace = V4L2_COLORSPACE_SRGB,
.priv = 0
}
}
};
/* If NULL, only single value to write, stored in len */
struct stv_init {
const u8 *data;
u16 start;
u8 len;
};
static const u8 x1500[] = { /* 0x1500 - 0x150f */
0x0b, 0xa7, 0xb7, 0x00, 0x00
};
static const u8 x1536[] = { /* 0x1536 - 0x153b */
0x02, 0x00, 0x60, 0x01, 0x20, 0x01
};
static const u8 x15c1[] = { /* 0x15c1 - 0x15c2 */
0xff, 0x03 /* Output word 0x03ff = 1023 (ISO size) */
};
static const struct stv_init stv_bridge_init[] = {
/* This reg is written twice. Some kind of reset? */
{NULL, 0x1620, 0x80},
{NULL, 0x1620, 0x00},
{NULL, 0x1423, 0x04},
{x1500, 0x1500, ARRAY_SIZE(x1500)},
{x1536, 0x1536, ARRAY_SIZE(x1536)},
{x15c1, 0x15c1, ARRAY_SIZE(x15c1)}
};
static const u8 vv6410_sensor_init[][2] = {
/* Setup registers */
{VV6410_SETUP0, VV6410_SOFT_RESET},
{VV6410_SETUP0, VV6410_LOW_POWER_MODE},
/* Use shuffled read-out mode */
{VV6410_SETUP1, BIT(6)},
/* All modes to 1 */
{VV6410_FGMODES, BIT(6) | BIT(4) | BIT(2) | BIT(0)},
{VV6410_PINMAPPING, 0x00},
/* Pre-clock generator divide off */
{VV6410_DATAFORMAT, BIT(7) | BIT(0)},
/* Exposure registers */
{VV6410_FINEH, VV6410_FINE_EXPOSURE >> 8},
{VV6410_FINEL, VV6410_FINE_EXPOSURE & 0xff},
{VV6410_COARSEH, VV6410_COARSE_EXPOSURE >> 8},
{VV6410_COARSEL, VV6410_COARSE_EXPOSURE & 0xff},
{VV6410_ANALOGGAIN, 0xf0 | VV6410_DEFAULT_GAIN},
{VV6410_CLKDIV, VV6410_CLK_DIV_2},
/* System registers */
/* Enable voltage doubler */
{VV6410_AS0, BIT(6) | BIT(4) | BIT(3) | BIT(2) | BIT(1)},
{VV6410_AT0, 0x00},
/* Power up audio, differential */
{VV6410_AT1, BIT(4)|BIT(0)},
};
#endif
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