2019-05-27 14:55:06 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2008-12-29 18:35:23 +08:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2011-08-22 06:56:57 +08:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2008-12-29 18:35:23 +08:00
|
|
|
#include "stv06xx_vv6410.h"
|
|
|
|
|
2009-01-01 01:33:53 +08:00
|
|
|
static struct v4l2_pix_format vv6410_mode[] = {
|
|
|
|
{
|
|
|
|
356,
|
|
|
|
292,
|
|
|
|
V4L2_PIX_FMT_SGRBG8,
|
|
|
|
V4L2_FIELD_NONE,
|
|
|
|
.sizeimage = 356 * 292,
|
|
|
|
.bytesperline = 356,
|
|
|
|
.colorspace = V4L2_COLORSPACE_SRGB,
|
|
|
|
.priv = 0
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-05-06 20:28:30 +08:00
|
|
|
static int vv6410_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
|
|
{
|
2012-05-09 22:19:00 +08:00
|
|
|
struct gspca_dev *gspca_dev =
|
|
|
|
container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
|
2012-05-06 20:28:30 +08:00
|
|
|
int err = -EINVAL;
|
|
|
|
|
|
|
|
switch (ctrl->id) {
|
|
|
|
case V4L2_CID_HFLIP:
|
2012-11-22 19:59:06 +08:00
|
|
|
if (!gspca_dev->streaming)
|
|
|
|
return 0;
|
2012-05-09 22:19:00 +08:00
|
|
|
err = vv6410_set_hflip(gspca_dev, ctrl->val);
|
2012-05-06 20:28:30 +08:00
|
|
|
break;
|
|
|
|
case V4L2_CID_VFLIP:
|
2012-11-22 19:59:06 +08:00
|
|
|
if (!gspca_dev->streaming)
|
|
|
|
return 0;
|
2012-05-09 22:19:00 +08:00
|
|
|
err = vv6410_set_vflip(gspca_dev, ctrl->val);
|
2012-05-06 20:28:30 +08:00
|
|
|
break;
|
|
|
|
case V4L2_CID_GAIN:
|
2012-05-09 22:19:00 +08:00
|
|
|
err = vv6410_set_analog_gain(gspca_dev, ctrl->val);
|
2012-05-06 20:28:30 +08:00
|
|
|
break;
|
|
|
|
case V4L2_CID_EXPOSURE:
|
2012-05-09 22:19:00 +08:00
|
|
|
err = vv6410_set_exposure(gspca_dev, ctrl->val);
|
2012-05-06 20:28:30 +08:00
|
|
|
break;
|
2009-01-01 01:33:53 +08:00
|
|
|
}
|
2012-05-06 20:28:30 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct v4l2_ctrl_ops vv6410_ctrl_ops = {
|
|
|
|
.s_ctrl = vv6410_s_ctrl,
|
|
|
|
};
|
2009-01-01 01:33:53 +08:00
|
|
|
|
2008-12-29 18:35:23 +08:00
|
|
|
static int vv6410_probe(struct sd *sd)
|
|
|
|
{
|
|
|
|
u16 data;
|
2012-05-06 20:28:30 +08:00
|
|
|
int err;
|
2008-12-29 18:35:23 +08:00
|
|
|
|
|
|
|
err = stv06xx_read_sensor(sd, VV6410_DEVICEH, &data);
|
|
|
|
if (err < 0)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2012-05-06 20:28:30 +08:00
|
|
|
if (data != 0x19)
|
|
|
|
return -ENODEV;
|
2008-12-29 18:35:23 +08:00
|
|
|
|
2012-05-06 20:28:30 +08:00
|
|
|
pr_info("vv6410 sensor detected\n");
|
2009-01-07 17:11:50 +08:00
|
|
|
|
2012-05-06 20:28:30 +08:00
|
|
|
sd->gspca_dev.cam.cam_mode = vv6410_mode;
|
|
|
|
sd->gspca_dev.cam.nmodes = ARRAY_SIZE(vv6410_mode);
|
|
|
|
return 0;
|
|
|
|
}
|
2009-01-07 17:11:50 +08:00
|
|
|
|
2012-05-06 20:28:30 +08:00
|
|
|
static int vv6410_init_controls(struct sd *sd)
|
|
|
|
{
|
2012-05-09 22:19:00 +08:00
|
|
|
struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
|
2012-05-06 20:28:30 +08:00
|
|
|
|
2012-12-11 03:35:19 +08:00
|
|
|
v4l2_ctrl_handler_init(hdl, 2);
|
|
|
|
/* Disable the hardware VFLIP and HFLIP as we currently lack a
|
|
|
|
mechanism to adjust the image offset in such a way that
|
|
|
|
we don't need to renegotiate the announced format */
|
|
|
|
/* v4l2_ctrl_new_std(hdl, &vv6410_ctrl_ops, */
|
|
|
|
/* V4L2_CID_HFLIP, 0, 1, 1, 0); */
|
|
|
|
/* v4l2_ctrl_new_std(hdl, &vv6410_ctrl_ops, */
|
|
|
|
/* V4L2_CID_VFLIP, 0, 1, 1, 0); */
|
2012-05-06 20:28:30 +08:00
|
|
|
v4l2_ctrl_new_std(hdl, &vv6410_ctrl_ops,
|
|
|
|
V4L2_CID_EXPOSURE, 0, 32768, 1, 20000);
|
|
|
|
v4l2_ctrl_new_std(hdl, &vv6410_ctrl_ops,
|
|
|
|
V4L2_CID_GAIN, 0, 15, 1, 10);
|
|
|
|
return hdl->error;
|
2008-12-29 18:35:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int vv6410_init(struct sd *sd)
|
|
|
|
{
|
|
|
|
int err = 0, i;
|
|
|
|
|
2012-05-06 20:28:30 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(stv_bridge_init); i++)
|
2011-07-28 04:19:58 +08:00
|
|
|
stv06xx_write_bridge(sd, stv_bridge_init[i].addr, stv_bridge_init[i].data);
|
2008-12-29 18:35:23 +08:00
|
|
|
|
|
|
|
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;
|
2013-02-05 00:17:55 +08:00
|
|
|
struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
|
2008-12-29 18:35:23 +08:00
|
|
|
struct cam *cam = &sd->gspca_dev.cam;
|
|
|
|
u32 priv = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
|
|
|
|
|
|
|
|
if (priv & VV6410_SUBSAMPLE) {
|
2017-09-23 03:20:33 +08:00
|
|
|
gspca_dbg(gspca_dev, D_CONF, "Enabling subsampling\n");
|
2008-12-29 18:35:23 +08:00
|
|
|
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);
|
2011-07-28 04:21:49 +08:00
|
|
|
stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x00);
|
2008-12-29 18:35:23 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
2017-09-23 03:20:33 +08:00
|
|
|
gspca_dbg(gspca_dev, D_STREAM, "Starting stream\n");
|
2008-12-29 18:35:23 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vv6410_stop(struct sd *sd)
|
|
|
|
{
|
2013-02-05 00:17:55 +08:00
|
|
|
struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
|
2008-12-29 18:35:23 +08:00
|
|
|
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;
|
|
|
|
|
2017-09-23 03:20:33 +08:00
|
|
|
gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
|
2008-12-29 18:35:23 +08:00
|
|
|
|
2014-02-18 23:00:45 +08:00
|
|
|
return 0;
|
2008-12-29 18:35:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int vv6410_dump(struct sd *sd)
|
|
|
|
{
|
|
|
|
u8 i;
|
|
|
|
int err = 0;
|
|
|
|
|
2011-08-22 06:56:57 +08:00
|
|
|
pr_info("Dumping all vv6410 sensor registers\n");
|
2008-12-29 18:35:23 +08:00
|
|
|
for (i = 0; i < 0xff && !err; i++) {
|
|
|
|
u16 data;
|
|
|
|
err = stv06xx_read_sensor(sd, i, &data);
|
2011-08-22 06:56:57 +08:00
|
|
|
pr_info("Register 0x%x contained 0x%x\n", i, data);
|
2008-12-29 18:35:23 +08:00
|
|
|
}
|
|
|
|
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;
|
2009-01-07 17:11:50 +08:00
|
|
|
|
2008-12-29 18:35:23 +08:00
|
|
|
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;
|
|
|
|
|
2017-09-23 03:20:33 +08:00
|
|
|
gspca_dbg(gspca_dev, D_CONF, "Set horizontal flip to %d\n", val);
|
2008-12-29 18:35:23 +08:00
|
|
|
err = stv06xx_write_sensor(sd, VV6410_DATAFORMAT, i2c_data);
|
|
|
|
|
|
|
|
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;
|
2009-01-07 17:11:50 +08:00
|
|
|
|
2008-12-29 18:35:23 +08:00
|
|
|
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;
|
|
|
|
|
2017-09-23 03:20:33 +08:00
|
|
|
gspca_dbg(gspca_dev, D_CONF, "Set vertical flip to %d\n", val);
|
2008-12-29 18:35:23 +08:00
|
|
|
err = stv06xx_write_sensor(sd, VV6410_DATAFORMAT, i2c_data);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2017-09-23 03:20:33 +08:00
|
|
|
gspca_dbg(gspca_dev, D_CONF, "Set analog gain to %d\n", val);
|
2008-12-29 18:35:23 +08:00
|
|
|
err = stv06xx_write_sensor(sd, VV6410_ANALOGGAIN, 0xf0 | (val & 0xf));
|
|
|
|
|
|
|
|
return (err < 0) ? err : 0;
|
|
|
|
}
|
2009-05-04 02:45:48 +08:00
|
|
|
|
|
|
|
static int vv6410_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct sd *sd = (struct sd *) gspca_dev;
|
|
|
|
unsigned int fine, coarse;
|
|
|
|
|
|
|
|
val = (val * val >> 14) + val / 4;
|
|
|
|
|
|
|
|
fine = val % VV6410_CIF_LINELENGTH;
|
|
|
|
coarse = min(512, val / VV6410_CIF_LINELENGTH);
|
|
|
|
|
2017-09-23 03:20:33 +08:00
|
|
|
gspca_dbg(gspca_dev, D_CONF, "Set coarse exposure to %d, fine exposure to %d\n",
|
|
|
|
coarse, fine);
|
2009-05-04 02:45:48 +08:00
|
|
|
|
|
|
|
err = stv06xx_write_sensor(sd, VV6410_FINEH, fine >> 8);
|
|
|
|
if (err < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
err = stv06xx_write_sensor(sd, VV6410_FINEL, fine & 0xff);
|
|
|
|
if (err < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
err = stv06xx_write_sensor(sd, VV6410_COARSEH, coarse >> 8);
|
|
|
|
if (err < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
err = stv06xx_write_sensor(sd, VV6410_COARSEL, coarse & 0xff);
|
|
|
|
|
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
}
|