summaryrefslogtreecommitdiffstats
path: root/drivers/media/usb/gspca/stv06xx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /drivers/media/usb/gspca/stv06xx
parentInitial commit. (diff)
downloadlinux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz
linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--drivers/media/usb/gspca/stv06xx/Kconfig10
-rw-r--r--drivers/media/usb/gspca/stv06xx/Makefile11
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx.c647
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx.h103
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.c531
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.h193
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.c432
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.h131
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_sensor.h74
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_st6422.c273
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_st6422.h38
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.c265
-rw-r--r--drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.h242
13 files changed, 2950 insertions, 0 deletions
diff --git a/drivers/media/usb/gspca/stv06xx/Kconfig b/drivers/media/usb/gspca/stv06xx/Kconfig
new file mode 100644
index 000000000..59373659a
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+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.
diff --git a/drivers/media/usb/gspca/stv06xx/Makefile b/drivers/media/usb/gspca/stv06xx/Makefile
new file mode 100644
index 000000000..c4d7206e0
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_USB_STV06XX) += gspca_stv06xx.o
+
+gspca_stv06xx-objs := stv06xx.o \
+ stv06xx_vv6410.o \
+ stv06xx_hdcs.o \
+ stv06xx_pb0100.o \
+ stv06xx_st6422.o
+
+ccflags-y += -I$(srctree)/drivers/media/usb/gspca
+
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx.c b/drivers/media/usb/gspca/stv06xx/stv06xx.c
new file mode 100644
index 000000000..d9bc2aacc
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx.c
@@ -0,0 +1,647 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/input.h>
+#include "stv06xx_sensor.h"
+
+MODULE_AUTHOR("Erik Andrén");
+MODULE_DESCRIPTION("STV06XX USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+static bool dump_bridge;
+static bool dump_sensor;
+
+int stv06xx_write_bridge(struct sd *sd, u16 address, u16 i2c_data)
+{
+ int err;
+ struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+ 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);
+
+ gspca_dbg(gspca_dev, D_CONF, "Written 0x%x to address 0x%x, status: %d\n",
+ i2c_data, address, err);
+
+ return (err < 0) ? err : 0;
+}
+
+int stv06xx_read_bridge(struct sd *sd, u16 address, u8 *i2c_data)
+{
+ int err;
+ struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+ 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];
+
+ gspca_dbg(gspca_dev, D_CONF, "Reading 0x%x from address 0x%x, status %d\n",
+ *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 (sd->bridge == BRIDGE_STV610) {
+ struct usb_device *udev = sd->gspca_dev.dev;
+ __u8 *buf = sd->gspca_dev.usb_buf;
+
+ 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 gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+ struct usb_device *udev = sd->gspca_dev.dev;
+ __u8 *buf = sd->gspca_dev.usb_buf;
+
+ gspca_dbg(gspca_dev, D_CONF, "I2C: Command buffer contains %d entries\n",
+ 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];
+ gspca_dbg(gspca_dev, D_CONF, "I2C: Writing 0x%02x to reg 0x%02x\n",
+ 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 gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+ struct usb_device *udev = sd->gspca_dev.dev;
+ __u8 *buf = sd->gspca_dev.usb_buf;
+
+ gspca_dbg(gspca_dev, D_CONF, "I2C: Command buffer contains %d entries\n",
+ 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;
+ gspca_dbg(gspca_dev, D_CONF, "I2C: Writing 0x%04x to reg 0x%02x\n",
+ 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 gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+ 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) {
+ pr_err("I2C: Read error writing address: %d\n", 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];
+
+ gspca_dbg(gspca_dev, D_CONF, "I2C: Read 0x%x from address 0x%x, status: %d\n",
+ *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;
+
+ pr_info("Dumping all stv06xx bridge registers\n");
+ for (i = 0x1400; i < 0x160f; i++) {
+ stv06xx_read_bridge(sd, i, &data);
+
+ pr_info("Read 0x%x from address 0x%x\n", data, i);
+ }
+
+ pr_info("Testing stv06xx bridge registers for writability\n");
+ 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)
+ pr_info("Register 0x%x is read/write\n", i);
+ else if (data != buf)
+ pr_info("Register 0x%x is read/write, but only partially\n",
+ i);
+ else
+ pr_info("Register 0x%x is read-only\n", 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;
+
+ gspca_dbg(gspca_dev, D_PROBE, "Initializing camera\n");
+
+ /* 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->sensor->dump(sd);
+
+ return (err < 0) ? err : 0;
+}
+
+/* this function is called at probe time */
+static int stv06xx_init_controls(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+
+ gspca_dbg(gspca_dev, D_PROBE, "Initializing controls\n");
+
+ gspca_dev->vdev.ctrl_handler = &gspca_dev->ctrl_handler;
+ return sd->sensor->init_controls(sd);
+}
+
+/* Start the camera */
+static int stv06xx_start(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct usb_host_interface *alt;
+ struct usb_interface *intf;
+ int err, packet_size;
+
+ intf = usb_ifnum_to_if(sd->gspca_dev.dev, sd->gspca_dev.iface);
+ alt = usb_altnum_to_altsetting(intf, sd->gspca_dev.alt);
+ if (!alt) {
+ gspca_err(gspca_dev, "Couldn't get altsetting\n");
+ return -EIO;
+ }
+
+ if (alt->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+ err = stv06xx_write_bridge(sd, STV_ISO_SIZE_L, packet_size);
+ if (err < 0)
+ return 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)
+ gspca_dbg(gspca_dev, D_STREAM, "Starting stream failed\n");
+ else
+ gspca_dbg(gspca_dev, D_STREAM, "Started streaming\n");
+
+ return (err < 0) ? err : 0;
+}
+
+static int stv06xx_isoc_init(struct gspca_dev *gspca_dev)
+{
+ struct usb_interface_cache *intfc;
+ struct usb_host_interface *alt;
+ struct sd *sd = (struct sd *) gspca_dev;
+
+ intfc = gspca_dev->dev->actconfig->intf_cache[0];
+
+ if (intfc->num_altsetting < 2)
+ return -ENODEV;
+
+ alt = &intfc->altsetting[1];
+
+ if (alt->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ /* Start isoc bandwidth "negotiation" at max isoc bandwidth */
+ alt->endpoint[0].desc.wMaxPacketSize =
+ cpu_to_le16(sd->sensor->max_packet_size[gspca_dev->curr_mode]);
+
+ return 0;
+}
+
+static int stv06xx_isoc_nego(struct gspca_dev *gspca_dev)
+{
+ int ret, packet_size, min_packet_size;
+ struct usb_host_interface *alt;
+ struct sd *sd = (struct sd *) gspca_dev;
+
+ /*
+ * Existence of altsetting and endpoint was verified in
+ * stv06xx_isoc_init()
+ */
+ alt = &gspca_dev->dev->actconfig->intf_cache[0]->altsetting[1];
+ packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+ min_packet_size = sd->sensor->min_packet_size[gspca_dev->curr_mode];
+ if (packet_size <= min_packet_size)
+ return -EIO;
+
+ packet_size -= 100;
+ if (packet_size < min_packet_size)
+ packet_size = min_packet_size;
+ alt->endpoint[0].desc.wMaxPacketSize = cpu_to_le16(packet_size);
+
+ ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, 1);
+ if (ret < 0)
+ gspca_err(gspca_dev, "set alt 1 err %d\n", ret);
+
+ return ret;
+}
+
+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);
+
+out:
+ if (err < 0)
+ gspca_dbg(gspca_dev, D_STREAM, "Failed to stop stream\n");
+ else
+ gspca_dbg(gspca_dev, D_STREAM, "Stopped streaming\n");
+}
+
+/*
+ * 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,
+ u8 *data, /* isoc packet */
+ int len) /* iso packet length */
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+
+ gspca_dbg(gspca_dev, D_PACK, "Packet of length %d arrived\n", len);
+
+ /* A packet may contain several frames
+ loop until the whole packet is reached */
+ while (len) {
+ int id, chunk_len;
+
+ if (len < 4) {
+ gspca_dbg(gspca_dev, D_PACK, "Packet is smaller than 4 bytes\n");
+ return;
+ }
+
+ /* Capture the id */
+ id = (data[0] << 8) | data[1];
+
+ /* Capture the chunk length */
+ chunk_len = (data[2] << 8) | data[3];
+ gspca_dbg(gspca_dev, D_PACK, "Chunk id: %x, length: %d\n",
+ id, chunk_len);
+
+ data += 4;
+ len -= 4;
+
+ if (len < chunk_len) {
+ gspca_err(gspca_dev, "URB packet length is smaller than the specified chunk length\n");
+ gspca_dev->last_packet_type = DISCARD_PACKET;
+ return;
+ }
+
+ /* First byte seem to be 02=data 2nd byte is unknown??? */
+ if (sd->bridge == BRIDGE_ST6422 && (id & 0xff00) == 0x0200)
+ goto frame_data;
+
+ switch (id) {
+ case 0x0200:
+ case 0x4200:
+frame_data:
+ gspca_dbg(gspca_dev, D_PACK, "Frame data packet detected\n");
+
+ if (sd->to_skip) {
+ int skip = (sd->to_skip < chunk_len) ?
+ sd->to_skip : chunk_len;
+ data += skip;
+ len -= skip;
+ chunk_len -= skip;
+ sd->to_skip -= skip;
+ }
+
+ gspca_frame_add(gspca_dev, INTER_PACKET,
+ data, chunk_len);
+ break;
+
+ case 0x8001:
+ case 0x8005:
+ case 0xc001:
+ case 0xc005:
+ gspca_dbg(gspca_dev, D_PACK, "Starting new frame\n");
+
+ /* Create a new frame, chunk length should be zero */
+ gspca_frame_add(gspca_dev, FIRST_PACKET,
+ NULL, 0);
+
+ if (sd->bridge == BRIDGE_ST6422)
+ sd->to_skip = gspca_dev->pixfmt.width * 4;
+
+ if (chunk_len)
+ gspca_err(gspca_dev, "Chunk length is non-zero on a SOF\n");
+ break;
+
+ case 0x8002:
+ case 0x8006:
+ case 0xc002:
+ gspca_dbg(gspca_dev, D_PACK, "End of frame detected\n");
+
+ /* Complete the last frame (if any) */
+ gspca_frame_add(gspca_dev, LAST_PACKET,
+ NULL, 0);
+
+ if (chunk_len)
+ gspca_err(gspca_dev, "Chunk length is non-zero on a EOF\n");
+ break;
+
+ case 0x0005:
+ gspca_dbg(gspca_dev, D_PACK, "Chunk 0x005 detected\n");
+ /* Unknown chunk with 11 bytes of data,
+ occurs just before end of each frame
+ in compressed mode */
+ break;
+
+ case 0x0100:
+ gspca_dbg(gspca_dev, D_PACK, "Chunk 0x0100 detected\n");
+ /* Unknown chunk with 2 bytes of data,
+ occurs 2-3 times per USB interrupt */
+ break;
+ case 0x42ff:
+ gspca_dbg(gspca_dev, D_PACK, "Chunk 0x42ff detected\n");
+ /* Special chunk seen sometimes on the ST6422 */
+ break;
+ default:
+ gspca_dbg(gspca_dev, D_PACK, "Unknown chunk 0x%04x detected\n",
+ id);
+ /* Unknown chunk */
+ }
+ data += chunk_len;
+ len -= chunk_len;
+ }
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+ u8 *data, /* interrupt packet data */
+ int len) /* interrupt packet length */
+{
+ int ret = -EINVAL;
+
+ if (len == 1 && (data[0] == 0x80 || data[0] == 0x10)) {
+ input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+ input_sync(gspca_dev->input_dev);
+ ret = 0;
+ }
+
+ if (len == 1 && (data[0] == 0x88 || data[0] == 0x11)) {
+ input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+ input_sync(gspca_dev->input_dev);
+ ret = 0;
+ }
+
+ return ret;
+}
+#endif
+
+static int stv06xx_config(struct gspca_dev *gspca_dev,
+ const struct usb_device_id *id);
+
+static void stv06xx_probe_error(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *)gspca_dev;
+
+ kfree(sd->sensor_priv);
+ sd->sensor_priv = NULL;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+ .name = MODULE_NAME,
+ .config = stv06xx_config,
+ .init = stv06xx_init,
+ .init_controls = stv06xx_init_controls,
+ .probe_error = stv06xx_probe_error,
+ .start = stv06xx_start,
+ .stopN = stv06xx_stopN,
+ .pkt_scan = stv06xx_pkt_scan,
+ .isoc_init = stv06xx_isoc_init,
+ .isoc_nego = stv06xx_isoc_nego,
+#if IS_ENABLED(CONFIG_INPUT)
+ .int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* 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;
+
+ gspca_dbg(gspca_dev, D_PROBE, "Configuring camera\n");
+
+ sd->bridge = id->driver_info;
+ gspca_dev->sd_desc = &sd_desc;
+
+ if (dump_bridge)
+ stv06xx_dump_bridge(sd);
+
+ sd->sensor = &stv06xx_sensor_st6422;
+ if (!sd->sensor->probe(sd))
+ return 0;
+
+ 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 struct usb_device_id device_table[] = {
+ {USB_DEVICE(0x046d, 0x0840), .driver_info = BRIDGE_STV600 }, /* QuickCam Express */
+ {USB_DEVICE(0x046d, 0x0850), .driver_info = BRIDGE_STV610 }, /* LEGO cam / QuickCam Web */
+ {USB_DEVICE(0x046d, 0x0870), .driver_info = BRIDGE_STV602 }, /* Dexxa WebCam USB */
+ {USB_DEVICE(0x046D, 0x08F0), .driver_info = BRIDGE_ST6422 }, /* QuickCam Messenger */
+ {USB_DEVICE(0x046D, 0x08F5), .driver_info = BRIDGE_ST6422 }, /* QuickCam Communicate */
+ {USB_DEVICE(0x046D, 0x08F6), .driver_info = BRIDGE_ST6422 }, /* QuickCam Messenger (new) */
+ {}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+ THIS_MODULE);
+}
+
+static void sd_disconnect(struct usb_interface *intf)
+{
+ struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
+ struct sd *sd = (struct sd *) gspca_dev;
+ void *priv = sd->sensor_priv;
+ gspca_dbg(gspca_dev, D_PROBE, "Disconnecting the stv06xx device\n");
+
+ sd->sensor = NULL;
+ gspca_disconnect(intf);
+ kfree(priv);
+}
+
+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,
+ .reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
+
+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");
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx.h b/drivers/media/usb/gspca/stv06xx/stv06xx.h
new file mode 100644
index 000000000..f6bafa982
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * 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
+ */
+
+#ifndef STV06XX_H_
+#define STV06XX_H_
+
+#include <linux/slab.h>
+#include "gspca.h"
+
+#define MODULE_NAME "STV06xx"
+
+#define STV_ISOC_ENDPOINT_ADDR 0x81
+
+#define STV_R 0x0509
+
+#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
+
+#define STV_RESET 0x1620
+
+/* 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;
+
+ /* Sensor private data */
+ void *sensor_priv;
+
+ /* The first 4 lines produced by the stv6422 are no good, this keeps
+ track of how many bytes we still need to skip during a frame */
+ int to_skip;
+
+ /* Bridge / Camera type */
+ u8 bridge;
+ #define BRIDGE_STV600 0
+ #define BRIDGE_STV602 1
+ #define BRIDGE_STV610 2
+ #define BRIDGE_ST6422 3 /* With integrated sensor */
+};
+
+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
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.c b/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.c
new file mode 100644
index 000000000..5a47dcbf1
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.c
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stv06xx_hdcs.h"
+
+static struct v4l2_pix_format hdcs1x00_mode[] = {
+ {
+ HDCS_1X00_DEF_WIDTH,
+ HDCS_1X00_DEF_HEIGHT,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_FIELD_NONE,
+ .sizeimage =
+ HDCS_1X00_DEF_WIDTH * HDCS_1X00_DEF_HEIGHT,
+ .bytesperline = HDCS_1X00_DEF_WIDTH,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 1
+ }
+};
+
+static struct v4l2_pix_format hdcs1020_mode[] = {
+ {
+ HDCS_1020_DEF_WIDTH,
+ HDCS_1020_DEF_HEIGHT,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_FIELD_NONE,
+ .sizeimage =
+ HDCS_1020_DEF_WIDTH * HDCS_1020_DEF_HEIGHT,
+ .bytesperline = HDCS_1020_DEF_WIDTH,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 1
+ }
+};
+
+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++) {
+ regs[2 * i] = reg;
+ regs[2 * i + 1] = vals[i];
+ /* All addresses are shifted left one bit
+ * as bit 0 toggles r/w */
+ reg += 2;
+ }
+
+ 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);
+
+ /* Update the state if the write succeeded */
+ if (!ret)
+ 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_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[14];
+
+ cycles = val * HDCS_CLK_FREQ_MHZ * 257;
+
+ 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] = HDCS20_CONTROL;
+ exp[1] = 0x00; /* Stop streaming */
+ exp[2] = HDCS_ROWEXPL;
+ exp[3] = rowexp & 0xff;
+ exp[4] = HDCS_ROWEXPH;
+ exp[5] = rowexp >> 8;
+ exp[6] = HDCS20_SROWEXP;
+ exp[7] = (srowexp >> 2) & 0xff;
+ exp[8] = HDCS20_ERROR;
+ exp[9] = 0x10; /* Clear exposure error flag*/
+ exp[10] = HDCS20_CONTROL;
+ exp[11] = 0x04; /* Restart streaming */
+ err = stv06xx_write_sensor_bytes(sd, exp, 6);
+ } else {
+ exp[0] = HDCS00_CONTROL;
+ exp[1] = 0x00; /* Stop streaming */
+ exp[2] = HDCS_ROWEXPL;
+ exp[3] = rowexp & 0xff;
+ exp[4] = HDCS_ROWEXPH;
+ exp[5] = rowexp >> 8;
+ exp[6] = HDCS00_SROWEXPL;
+ exp[7] = srowexp & 0xff;
+ exp[8] = HDCS00_SROWEXPH;
+ exp[9] = srowexp >> 8;
+ exp[10] = HDCS_STATUS;
+ exp[11] = 0x10; /* Clear exposure error flag*/
+ exp[12] = HDCS00_CONTROL;
+ exp[13] = 0x04; /* Restart streaming */
+ err = stv06xx_write_sensor_bytes(sd, exp, 7);
+ if (err < 0)
+ return err;
+ }
+ gspca_dbg(gspca_dev, D_CONF, "Writing exposure %d, rowexp %d, srowexp %d\n",
+ val, rowexp, srowexp);
+ return err;
+}
+
+static int hdcs_set_gains(struct sd *sd, u8 g)
+{
+ int err;
+ u8 gains[4];
+
+ /* the voltage gain Av = (1 + 19 * val / 127) * (1 + bit7) */
+ if (g > 127)
+ g = 0x80 | (g / 2);
+
+ gains[0] = g;
+ gains[1] = g;
+ gains[2] = g;
+ gains[3] = g;
+
+ err = hdcs_reg_write_seq(sd, HDCS_ERECPGA, gains, 4);
+ return err;
+}
+
+static int hdcs_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+ gspca_dbg(gspca_dev, D_CONF, "Writing gain %d\n", val);
+ return hdcs_set_gains((struct sd *) gspca_dev,
+ 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_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct gspca_dev *gspca_dev =
+ container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+ int err = -EINVAL;
+
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ err = hdcs_set_gain(gspca_dev, ctrl->val);
+ break;
+ case V4L2_CID_EXPOSURE:
+ err = hdcs_set_exposure(gspca_dev, ctrl->val);
+ break;
+ }
+ return err;
+}
+
+static const struct v4l2_ctrl_ops hdcs_ctrl_ops = {
+ .s_ctrl = hdcs_s_ctrl,
+};
+
+static int hdcs_init_controls(struct sd *sd)
+{
+ struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+ v4l2_ctrl_handler_init(hdl, 2);
+ v4l2_ctrl_new_std(hdl, &hdcs_ctrl_ops,
+ V4L2_CID_EXPOSURE, 0, 0xff, 1, HDCS_DEFAULT_EXPOSURE);
+ v4l2_ctrl_new_std(hdl, &hdcs_ctrl_ops,
+ V4L2_CID_GAIN, 0, 0xff, 1, HDCS_DEFAULT_GAIN);
+ return hdl->error;
+}
+
+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;
+
+ pr_info("HDCS-1000/1100 sensor detected\n");
+
+ sd->gspca_dev.cam.cam_mode = hdcs1x00_mode;
+ sd->gspca_dev.cam.nmodes = ARRAY_SIZE(hdcs1x00_mode);
+
+ 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 with STV600 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 with STV602 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 = (sd->bridge == BRIDGE_STV602) ? 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;
+
+ pr_info("HDCS-1020 sensor detected\n");
+
+ sd->gspca_dev.cam.cam_mode = hdcs1020_mode;
+ sd->gspca_dev.cam.nmodes = ARRAY_SIZE(hdcs1020_mode);
+
+ 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)
+{
+ struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+ gspca_dbg(gspca_dev, D_STREAM, "Starting stream\n");
+
+ return hdcs_set_state(sd, HDCS_STATE_RUN);
+}
+
+static int hdcs_stop(struct sd *sd)
+{
+ struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+ gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
+
+ return hdcs_set_state(sd, HDCS_STATE_SLEEP);
+}
+
+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 (sd->bridge == BRIDGE_STV602)
+ 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 continuous 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 the STV602, 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;
+
+ return hdcs_set_size(sd, hdcs->array.width, hdcs->array.height);
+}
+
+static int hdcs_dump(struct sd *sd)
+{
+ u16 reg, val;
+
+ pr_info("Dumping sensor registers:\n");
+
+ for (reg = HDCS_IDENT; reg <= HDCS_ROWEXPH; reg++) {
+ stv06xx_read_sensor(sd, reg, &val);
+ pr_info("reg 0x%02x = 0x%02x\n", reg, val);
+ }
+ return 0;
+}
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.h b/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.h
new file mode 100644
index 000000000..326a6eb68
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.h
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * 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
+ *
+ * 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 48
+#define HDCS_DEFAULT_GAIN 50
+
+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_init_controls(struct sd *sd);
+static int hdcs_stop(struct sd *sd);
+static int hdcs_dump(struct sd *sd);
+
+static int hdcs_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
+static int hdcs_set_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,
+
+ /* FIXME (see if we can lower min_packet_size, needs testing, and also
+ adjusting framerate when the bandwidth gets lower) */
+ .min_packet_size = { 847 },
+ .max_packet_size = { 847 },
+
+ .init = hdcs_init,
+ .init_controls = hdcs_init_controls,
+ .probe = hdcs_probe_1x00,
+ .start = hdcs_start,
+ .stop = hdcs_stop,
+ .dump = hdcs_dump,
+};
+
+const struct stv06xx_sensor stv06xx_sensor_hdcs1020 = {
+ .name = "HDCS-1020",
+ .i2c_flush = 0,
+ .i2c_addr = (0x55 << 1),
+ .i2c_len = 1,
+
+ /* FIXME (see if we can lower min_packet_size, needs testing, and also
+ adjusting framerate when the bandwidthm gets lower) */
+ .min_packet_size = { 847 },
+ .max_packet_size = { 847 },
+
+ .init = hdcs_init,
+ .init_controls = hdcs_init_controls,
+ .probe = hdcs_probe_1020,
+ .start = hdcs_start,
+ .stop = hdcs_stop,
+ .dump = hdcs_dump,
+};
+
+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_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
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.c b/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.c
new file mode 100644
index 000000000..ae382b3b5
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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
+ */
+
+/*
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stv06xx_pb0100.h"
+
+struct pb0100_ctrls {
+ struct { /* one big happy control cluster... */
+ struct v4l2_ctrl *autogain;
+ struct v4l2_ctrl *gain;
+ struct v4l2_ctrl *exposure;
+ struct v4l2_ctrl *red;
+ struct v4l2_ctrl *blue;
+ struct v4l2_ctrl *natural;
+ };
+ struct v4l2_ctrl *target;
+};
+
+static struct v4l2_pix_format pb0100_mode[] = {
+/* 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
+ }
+};
+
+static int pb0100_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct gspca_dev *gspca_dev =
+ container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+ struct sd *sd = (struct sd *)gspca_dev;
+ struct pb0100_ctrls *ctrls = sd->sensor_priv;
+ int err = -EINVAL;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUTOGAIN:
+ err = pb0100_set_autogain(gspca_dev, ctrl->val);
+ if (err)
+ break;
+ if (ctrl->val)
+ break;
+ err = pb0100_set_gain(gspca_dev, ctrls->gain->val);
+ if (err)
+ break;
+ err = pb0100_set_exposure(gspca_dev, ctrls->exposure->val);
+ break;
+ case V4L2_CTRL_CLASS_USER + 0x1001:
+ err = pb0100_set_autogain_target(gspca_dev, ctrl->val);
+ break;
+ }
+ return err;
+}
+
+static const struct v4l2_ctrl_ops pb0100_ctrl_ops = {
+ .s_ctrl = pb0100_s_ctrl,
+};
+
+static int pb0100_init_controls(struct sd *sd)
+{
+ struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+ struct pb0100_ctrls *ctrls;
+ static const struct v4l2_ctrl_config autogain_target = {
+ .ops = &pb0100_ctrl_ops,
+ .id = V4L2_CTRL_CLASS_USER + 0x1000,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Automatic Gain Target",
+ .max = 255,
+ .step = 1,
+ .def = 128,
+ };
+ static const struct v4l2_ctrl_config natural_light = {
+ .ops = &pb0100_ctrl_ops,
+ .id = V4L2_CTRL_CLASS_USER + 0x1001,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Natural Light Source",
+ .max = 1,
+ .step = 1,
+ .def = 1,
+ };
+
+ ctrls = kzalloc(sizeof(*ctrls), GFP_KERNEL);
+ if (!ctrls)
+ return -ENOMEM;
+
+ v4l2_ctrl_handler_init(hdl, 6);
+ ctrls->autogain = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+ V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+ ctrls->exposure = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+ V4L2_CID_EXPOSURE, 0, 511, 1, 12);
+ ctrls->gain = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+ V4L2_CID_GAIN, 0, 255, 1, 128);
+ ctrls->red = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+ V4L2_CID_RED_BALANCE, -255, 255, 1, 0);
+ ctrls->blue = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+ V4L2_CID_BLUE_BALANCE, -255, 255, 1, 0);
+ ctrls->natural = v4l2_ctrl_new_custom(hdl, &natural_light, NULL);
+ ctrls->target = v4l2_ctrl_new_custom(hdl, &autogain_target, NULL);
+ if (hdl->error) {
+ kfree(ctrls);
+ return hdl->error;
+ }
+ sd->sensor_priv = ctrls;
+ v4l2_ctrl_auto_cluster(5, &ctrls->autogain, 0, false);
+ return 0;
+}
+
+static int pb0100_probe(struct sd *sd)
+{
+ u16 sensor;
+ int err;
+
+ err = stv06xx_read_sensor(sd, PB_IDENT, &sensor);
+
+ if (err < 0)
+ return -ENODEV;
+ if ((sensor >> 8) != 0x64)
+ return -ENODEV;
+
+ pr_info("Photobit pb0100 sensor detected\n");
+
+ sd->gspca_dev.cam.cam_mode = pb0100_mode;
+ sd->gspca_dev.cam.nmodes = ARRAY_SIZE(pb0100_mode);
+
+ return 0;
+}
+
+static int pb0100_start(struct sd *sd)
+{
+ int err, packet_size, max_packet_size;
+ struct usb_host_interface *alt;
+ struct usb_interface *intf;
+ struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+ struct cam *cam = &sd->gspca_dev.cam;
+ u32 mode = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
+
+ intf = usb_ifnum_to_if(sd->gspca_dev.dev, sd->gspca_dev.iface);
+ alt = usb_altnum_to_altsetting(intf, sd->gspca_dev.alt);
+ if (!alt)
+ return -ENODEV;
+
+ if (alt->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+
+ /* If we don't have enough bandwidth use a lower framerate */
+ max_packet_size = sd->sensor->max_packet_size[sd->gspca_dev.curr_mode];
+ if (packet_size < max_packet_size)
+ stv06xx_write_sensor(sd, PB_ROWSPEED, BIT(4)|BIT(3)|BIT(1));
+ else
+ stv06xx_write_sensor(sd, PB_ROWSPEED, BIT(5)|BIT(3)|BIT(1));
+
+ /* 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);
+ }
+
+ err = stv06xx_write_sensor(sd, PB_CONTROL, BIT(5)|BIT(3)|BIT(1));
+ gspca_dbg(gspca_dev, D_STREAM, "Started stream, status: %d\n", err);
+
+ return (err < 0) ? err : 0;
+}
+
+static int pb0100_stop(struct sd *sd)
+{
+ struct gspca_dev *gspca_dev = (struct gspca_dev *)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));
+
+ gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
+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);
+
+ /* 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_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+ int err;
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct pb0100_ctrls *ctrls = sd->sensor_priv;
+
+ err = stv06xx_write_sensor(sd, PB_G1GAIN, val);
+ if (!err)
+ err = stv06xx_write_sensor(sd, PB_G2GAIN, val);
+ gspca_dbg(gspca_dev, D_CONF, "Set green gain to %d, status: %d\n",
+ val, err);
+
+ if (!err)
+ err = pb0100_set_red_balance(gspca_dev, ctrls->red->val);
+ if (!err)
+ err = pb0100_set_blue_balance(gspca_dev, ctrls->blue->val);
+
+ return err;
+}
+
+static int pb0100_set_red_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+ int err;
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct pb0100_ctrls *ctrls = sd->sensor_priv;
+
+ val += ctrls->gain->val;
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+
+ err = stv06xx_write_sensor(sd, PB_RGAIN, val);
+ gspca_dbg(gspca_dev, D_CONF, "Set red gain to %d, status: %d\n",
+ val, err);
+
+ return err;
+}
+
+static int pb0100_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+ int err;
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct pb0100_ctrls *ctrls = sd->sensor_priv;
+
+ val += ctrls->gain->val;
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+
+ err = stv06xx_write_sensor(sd, PB_BGAIN, val);
+ gspca_dbg(gspca_dev, D_CONF, "Set blue gain to %d, status: %d\n",
+ val, err);
+
+ return err;
+}
+
+static int pb0100_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+ int err;
+
+ err = stv06xx_write_sensor(sd, PB_RINTTIME, val);
+ gspca_dbg(gspca_dev, D_CONF, "Set exposure to %d, status: %d\n",
+ val, err);
+
+ return err;
+}
+
+static int pb0100_set_autogain(struct gspca_dev *gspca_dev, __s32 val)
+{
+ int err;
+ struct sd *sd = (struct sd *) gspca_dev;
+ struct pb0100_ctrls *ctrls = sd->sensor_priv;
+
+ if (val) {
+ if (ctrls->natural->val)
+ val = BIT(6)|BIT(4)|BIT(0);
+ else
+ val = BIT(4)|BIT(0);
+ } else
+ val = 0;
+
+ err = stv06xx_write_sensor(sd, PB_EXPGAIN, val);
+ gspca_dbg(gspca_dev, D_CONF, "Set autogain to %d (natural: %d), status: %d\n",
+ val, ctrls->natural->val, err);
+
+ return err;
+}
+
+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;
+
+ /* Number of pixels counted by the sensor when subsampling the pixels.
+ * Slightly larger than the real value to avoid oscillation */
+ totalpixels = gspca_dev->pixfmt.width * gspca_dev->pixfmt.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);
+
+ gspca_dbg(gspca_dev, D_CONF, "Set autogain target to %d, status: %d\n",
+ val, err);
+
+ return err;
+}
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.h b/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.h
new file mode 100644
index 000000000..c5a661457
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * 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
+ */
+
+#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_init_controls(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_set_gain(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_red_balance(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_autogain(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_autogain_target(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,
+
+ .min_packet_size = { 635, 847 },
+ .max_packet_size = { 847, 923 },
+
+ .init = pb0100_init,
+ .init_controls = pb0100_init_controls,
+ .probe = pb0100_probe,
+ .start = pb0100_start,
+ .stop = pb0100_stop,
+ .dump = pb0100_dump,
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_sensor.h b/drivers/media/usb/gspca/stv06xx/stv06xx_sensor.h
new file mode 100644
index 000000000..67c13c689
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_sensor.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * 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
+ */
+
+#ifndef STV06XX_SENSOR_H_
+#define STV06XX_SENSOR_H_
+
+#include "stv06xx.h"
+
+#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;
+extern const struct stv06xx_sensor stv06xx_sensor_st6422;
+
+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;
+
+ /* Isoc packet size (per mode) */
+ int min_packet_size[4];
+ int max_packet_size[4];
+
+ /* Probes if the sensor is connected */
+ int (*probe)(struct sd *sd);
+
+ /* Performs a initialization sequence */
+ int (*init)(struct sd *sd);
+
+ /* Initializes the controls */
+ int (*init_controls)(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);
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.c b/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.c
new file mode 100644
index 000000000..aac19d449
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for the sensor part which is integrated (I think) into the
+ * st6422 stv06xx alike bridge, as its integrated there are no i2c writes
+ * but instead direct bridge writes.
+ *
+ * Copyright (c) 2009 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Strongly based on qc-usb-messenger, which is:
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ * Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stv06xx_st6422.h"
+
+static struct v4l2_pix_format st6422_mode[] = {
+ /* Note we actually get 124 lines of data, of which we skip the 4st
+ 4 as they are garbage */
+ {
+ 162,
+ 120,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_FIELD_NONE,
+ .sizeimage = 162 * 120,
+ .bytesperline = 162,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 1
+ },
+ /* Note we actually get 248 lines of data, of which we skip the 4st
+ 4 as they are garbage, and we tell the app it only gets the
+ first 240 of the 244 lines it actually gets, so that it ignores
+ the last 4. */
+ {
+ 324,
+ 240,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_FIELD_NONE,
+ .sizeimage = 324 * 244,
+ .bytesperline = 324,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 0
+ },
+};
+
+/* V4L2 controls supported by the driver */
+static int setbrightness(struct sd *sd, s32 val);
+static int setcontrast(struct sd *sd, s32 val);
+static int setgain(struct sd *sd, u8 gain);
+static int setexposure(struct sd *sd, s16 expo);
+
+static int st6422_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct gspca_dev *gspca_dev =
+ container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+ struct sd *sd = (struct sd *)gspca_dev;
+ int err = -EINVAL;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ err = setbrightness(sd, ctrl->val);
+ break;
+ case V4L2_CID_CONTRAST:
+ err = setcontrast(sd, ctrl->val);
+ break;
+ case V4L2_CID_GAIN:
+ err = setgain(sd, ctrl->val);
+ break;
+ case V4L2_CID_EXPOSURE:
+ err = setexposure(sd, ctrl->val);
+ break;
+ }
+
+ /* commit settings */
+ if (err >= 0)
+ err = stv06xx_write_bridge(sd, 0x143f, 0x01);
+ sd->gspca_dev.usb_err = err;
+ return err;
+}
+
+static const struct v4l2_ctrl_ops st6422_ctrl_ops = {
+ .s_ctrl = st6422_s_ctrl,
+};
+
+static int st6422_init_controls(struct sd *sd)
+{
+ struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+ v4l2_ctrl_handler_init(hdl, 4);
+ v4l2_ctrl_new_std(hdl, &st6422_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, 0, 31, 1, 3);
+ v4l2_ctrl_new_std(hdl, &st6422_ctrl_ops,
+ V4L2_CID_CONTRAST, 0, 15, 1, 11);
+ v4l2_ctrl_new_std(hdl, &st6422_ctrl_ops,
+ V4L2_CID_EXPOSURE, 0, 1023, 1, 256);
+ v4l2_ctrl_new_std(hdl, &st6422_ctrl_ops,
+ V4L2_CID_GAIN, 0, 255, 1, 64);
+
+ return hdl->error;
+}
+
+static int st6422_probe(struct sd *sd)
+{
+ if (sd->bridge != BRIDGE_ST6422)
+ return -ENODEV;
+
+ pr_info("st6422 sensor detected\n");
+
+ sd->gspca_dev.cam.cam_mode = st6422_mode;
+ sd->gspca_dev.cam.nmodes = ARRAY_SIZE(st6422_mode);
+ return 0;
+}
+
+static int st6422_init(struct sd *sd)
+{
+ int err = 0, i;
+
+ static const u16 st6422_bridge_init[][2] = {
+ { STV_ISO_ENABLE, 0x00 }, /* disable capture */
+ { 0x1436, 0x00 },
+ { 0x1432, 0x03 }, /* 0x00-0x1F brightness */
+ { 0x143a, 0xf9 }, /* 0x00-0x0F contrast */
+ { 0x0509, 0x38 }, /* R */
+ { 0x050a, 0x38 }, /* G */
+ { 0x050b, 0x38 }, /* B */
+ { 0x050c, 0x2a },
+ { 0x050d, 0x01 },
+
+
+ { 0x1431, 0x00 }, /* 0x00-0x07 ??? */
+ { 0x1433, 0x34 }, /* 160x120, 0x00-0x01 night filter */
+ { 0x1438, 0x18 }, /* 640x480 */
+/* 18 bayes */
+/* 10 compressed? */
+
+ { 0x1439, 0x00 },
+/* anti-noise? 0xa2 gives a perfect image */
+
+ { 0x143b, 0x05 },
+ { 0x143c, 0x00 }, /* 0x00-0x01 - ??? */
+
+
+/* shutter time 0x0000-0x03FF */
+/* low value give good picures on moving objects (but requires much light) */
+/* high value gives good picures in darkness (but tends to be overexposed) */
+ { 0x143e, 0x01 },
+ { 0x143d, 0x00 },
+
+ { 0x1442, 0xe2 },
+/* write: 1x1x xxxx */
+/* read: 1x1x xxxx */
+/* bit 5 == button pressed and hold if 0 */
+/* write 0xe2,0xea */
+
+/* 0x144a */
+/* 0x00 init */
+/* bit 7 == button has been pressed, but not handled */
+
+/* interrupt */
+/* if(urb->iso_frame_desc[i].status == 0x80) { */
+/* if(urb->iso_frame_desc[i].status == 0x88) { */
+
+ { 0x1500, 0xd0 },
+ { 0x1500, 0xd0 },
+ { 0x1500, 0x50 }, /* 0x00 - 0xFF 0x80 == compr ? */
+
+ { 0x1501, 0xaf },
+/* high val-> light area gets darker */
+/* low val -> light area gets lighter */
+ { 0x1502, 0xc2 },
+/* high val-> light area gets darker */
+/* low val -> light area gets lighter */
+ { 0x1503, 0x45 },
+/* high val-> light area gets darker */
+/* low val -> light area gets lighter */
+ { 0x1505, 0x02 },
+/* 2 : 324x248 80352 bytes */
+/* 7 : 248x162 40176 bytes */
+/* c+f: 162*124 20088 bytes */
+
+ { 0x150e, 0x8e },
+ { 0x150f, 0x37 },
+ { 0x15c0, 0x00 },
+ { 0x15c3, 0x08 }, /* 0x04/0x14 ... test pictures ??? */
+
+
+ { 0x143f, 0x01 }, /* commit settings */
+
+ };
+
+ for (i = 0; i < ARRAY_SIZE(st6422_bridge_init) && !err; i++) {
+ err = stv06xx_write_bridge(sd, st6422_bridge_init[i][0],
+ st6422_bridge_init[i][1]);
+ }
+
+ return err;
+}
+
+static int setbrightness(struct sd *sd, s32 val)
+{
+ /* val goes from 0 -> 31 */
+ return stv06xx_write_bridge(sd, 0x1432, val);
+}
+
+static int setcontrast(struct sd *sd, s32 val)
+{
+ /* Val goes from 0 -> 15 */
+ return stv06xx_write_bridge(sd, 0x143a, val | 0xf0);
+}
+
+static int setgain(struct sd *sd, u8 gain)
+{
+ int err;
+
+ /* Set red, green, blue, gain */
+ err = stv06xx_write_bridge(sd, 0x0509, gain);
+ if (err < 0)
+ return err;
+
+ err = stv06xx_write_bridge(sd, 0x050a, gain);
+ if (err < 0)
+ return err;
+
+ err = stv06xx_write_bridge(sd, 0x050b, gain);
+ if (err < 0)
+ return err;
+
+ /* 2 mystery writes */
+ err = stv06xx_write_bridge(sd, 0x050c, 0x2a);
+ if (err < 0)
+ return err;
+
+ return stv06xx_write_bridge(sd, 0x050d, 0x01);
+}
+
+static int setexposure(struct sd *sd, s16 expo)
+{
+ int err;
+
+ err = stv06xx_write_bridge(sd, 0x143d, expo & 0xff);
+ if (err < 0)
+ return err;
+
+ return stv06xx_write_bridge(sd, 0x143e, expo >> 8);
+}
+
+static int st6422_start(struct sd *sd)
+{
+ int err;
+ struct cam *cam = &sd->gspca_dev.cam;
+
+ if (cam->cam_mode[sd->gspca_dev.curr_mode].priv)
+ err = stv06xx_write_bridge(sd, 0x1505, 0x0f);
+ else
+ err = stv06xx_write_bridge(sd, 0x1505, 0x02);
+ if (err < 0)
+ return err;
+
+ /* commit settings */
+ err = stv06xx_write_bridge(sd, 0x143f, 0x01);
+ return (err < 0) ? err : 0;
+}
+
+static int st6422_stop(struct sd *sd)
+{
+ struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+ gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
+
+ return 0;
+}
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.h b/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.h
new file mode 100644
index 000000000..68ba01c5a
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Support for the sensor part which is integrated (I think) into the
+ * st6422 stv06xx alike bridge, as its integrated there are no i2c writes
+ * but instead direct bridge writes.
+ *
+ * Copyright (c) 2009 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Strongly based on qc-usb-messenger, which is:
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ * Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ */
+
+#ifndef STV06XX_ST6422_H_
+#define STV06XX_ST6422_H_
+
+#include "stv06xx_sensor.h"
+
+static int st6422_probe(struct sd *sd);
+static int st6422_start(struct sd *sd);
+static int st6422_init(struct sd *sd);
+static int st6422_init_controls(struct sd *sd);
+static int st6422_stop(struct sd *sd);
+
+const struct stv06xx_sensor stv06xx_sensor_st6422 = {
+ .name = "ST6422",
+ /* No known way to lower framerate in case of less bandwidth */
+ .min_packet_size = { 300, 847 },
+ .max_packet_size = { 300, 847 },
+ .init = st6422_init,
+ .init_controls = st6422_init_controls,
+ .probe = st6422_probe,
+ .start = st6422_start,
+ .stop = st6422_stop,
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.c b/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.c
new file mode 100644
index 000000000..dde1950bd
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stv06xx_vv6410.h"
+
+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
+ }
+};
+
+static int vv6410_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct gspca_dev *gspca_dev =
+ container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+ int err = -EINVAL;
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ if (!gspca_dev->streaming)
+ return 0;
+ err = vv6410_set_hflip(gspca_dev, ctrl->val);
+ break;
+ case V4L2_CID_VFLIP:
+ if (!gspca_dev->streaming)
+ return 0;
+ err = vv6410_set_vflip(gspca_dev, ctrl->val);
+ break;
+ case V4L2_CID_GAIN:
+ err = vv6410_set_analog_gain(gspca_dev, ctrl->val);
+ break;
+ case V4L2_CID_EXPOSURE:
+ err = vv6410_set_exposure(gspca_dev, ctrl->val);
+ break;
+ }
+ return err;
+}
+
+static const struct v4l2_ctrl_ops vv6410_ctrl_ops = {
+ .s_ctrl = vv6410_s_ctrl,
+};
+
+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)
+ return -ENODEV;
+
+ pr_info("vv6410 sensor detected\n");
+
+ sd->gspca_dev.cam.cam_mode = vv6410_mode;
+ sd->gspca_dev.cam.nmodes = ARRAY_SIZE(vv6410_mode);
+ return 0;
+}
+
+static int vv6410_init_controls(struct sd *sd)
+{
+ struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+ 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); */
+ 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;
+}
+
+static int vv6410_init(struct sd *sd)
+{
+ int err = 0, i;
+
+ for (i = 0; i < ARRAY_SIZE(stv_bridge_init); i++)
+ stv06xx_write_bridge(sd, stv_bridge_init[i].addr, stv_bridge_init[i].data);
+
+ 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 gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+ struct cam *cam = &sd->gspca_dev.cam;
+ u32 priv = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
+
+ if (priv & VV6410_SUBSAMPLE) {
+ gspca_dbg(gspca_dev, D_CONF, "Enabling subsampling\n");
+ 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, 0x00);
+
+ }
+
+ /* 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;
+
+ gspca_dbg(gspca_dev, D_STREAM, "Starting stream\n");
+
+ return 0;
+}
+
+static int vv6410_stop(struct sd *sd)
+{
+ struct gspca_dev *gspca_dev = (struct gspca_dev *)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;
+
+ gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
+
+ return 0;
+}
+
+static int vv6410_dump(struct sd *sd)
+{
+ u8 i;
+ int err = 0;
+
+ pr_info("Dumping all vv6410 sensor registers\n");
+ for (i = 0; i < 0xff && !err; i++) {
+ u16 data;
+ err = stv06xx_read_sensor(sd, i, &data);
+ pr_info("Register 0x%x contained 0x%x\n", i, data);
+ }
+ 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;
+
+ gspca_dbg(gspca_dev, D_CONF, "Set horizontal flip to %d\n", val);
+ 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;
+
+ 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;
+
+ gspca_dbg(gspca_dev, D_CONF, "Set vertical flip to %d\n", val);
+ 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;
+
+ gspca_dbg(gspca_dev, D_CONF, "Set analog gain to %d\n", val);
+ err = stv06xx_write_sensor(sd, VV6410_ANALOGGAIN, 0xf0 | (val & 0xf));
+
+ return (err < 0) ? err : 0;
+}
+
+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);
+
+ gspca_dbg(gspca_dev, D_CONF, "Set coarse exposure to %d, fine exposure to %d\n",
+ coarse, fine);
+
+ 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;
+}
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.h b/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.h
new file mode 100644
index 000000000..343ddc304
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.h
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * 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
+ */
+
+#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
+
+#define VV6410_CIF_LINELENGTH 415
+
+static int vv6410_probe(struct sd *sd);
+static int vv6410_start(struct sd *sd);
+static int vv6410_init(struct sd *sd);
+static int vv6410_init_controls(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_set_hflip(struct gspca_dev *gspca_dev, __s32 val);
+static int vv6410_set_vflip(struct gspca_dev *gspca_dev, __s32 val);
+static int vv6410_set_analog_gain(struct gspca_dev *gspca_dev, __s32 val);
+static int vv6410_set_exposure(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,
+ /* FIXME (see if we can lower packet_size-s, needs testing, and also
+ adjusting framerate when the bandwidth gets lower) */
+ .min_packet_size = { 1023 },
+ .max_packet_size = { 1023 },
+ .init = vv6410_init,
+ .init_controls = vv6410_init_controls,
+ .probe = vv6410_probe,
+ .start = vv6410_start,
+ .stop = vv6410_stop,
+ .dump = vv6410_dump,
+};
+
+/* If NULL, only single value to write, stored in len */
+struct stv_init {
+ u16 addr;
+ u8 data;
+};
+
+static const struct stv_init stv_bridge_init[] = {
+ /* This reg is written twice. Some kind of reset? */
+ {STV_RESET, 0x80},
+ {STV_RESET, 0x00},
+ {STV_SCAN_RATE, 0x00},
+ {STV_I2C_FLUSH, 0x04},
+ {STV_REG00, 0x0b},
+ {STV_REG01, 0xa7},
+ {STV_REG02, 0xb7},
+ {STV_REG03, 0x00},
+ {STV_REG04, 0x00},
+ {0x1536, 0x02},
+ {0x1537, 0x00},
+ {0x1538, 0x60},
+ {0x1539, 0x01},
+ {0x153a, 0x20},
+ {0x153b, 0x01},
+};
+
+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, FST, Fast QCK, Free running QCK, Free running LST, FST will qualify visible pixels */
+ {VV6410_FGMODES, BIT(6) | BIT(4) | BIT(2) | BIT(0)},
+ {VV6410_PINMAPPING, 0x00},
+ /* Pre-clock generator divide off */
+ {VV6410_DATAFORMAT, BIT(7) | BIT(0)},
+
+ {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