162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *		Pixart PAC7311 library
462306a36Sopenharmony_ci *		Copyright (C) 2005 Thomas Kaiser thomas@kaiser-linux.li
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci/* Some documentation about various registers as determined by trial and error.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Register page 1:
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * Address	Description
1462306a36Sopenharmony_ci * 0x08		Unknown compressor related, must always be 8 except when not
1562306a36Sopenharmony_ci *		in 640x480 resolution and page 4 reg 2 <= 3 then set it to 9 !
1662306a36Sopenharmony_ci * 0x1b		Auto white balance related, bit 0 is AWB enable (inverted)
1762306a36Sopenharmony_ci *		bits 345 seem to toggle per color gains on/off (inverted)
1862306a36Sopenharmony_ci * 0x78		Global control, bit 6 controls the LED (inverted)
1962306a36Sopenharmony_ci * 0x80		Compression balance, interesting settings:
2062306a36Sopenharmony_ci *		0x01 Use this to allow the camera to switch to higher compr.
2162306a36Sopenharmony_ci *		     on the fly. Needed to stay within bandwidth @ 640x480@30
2262306a36Sopenharmony_ci *		0x1c From usb captures under Windows for 640x480
2362306a36Sopenharmony_ci *		0x2a Values >= this switch the camera to a lower compression,
2462306a36Sopenharmony_ci *		     using the same table for both luminance and chrominance.
2562306a36Sopenharmony_ci *		     This gives a sharper picture. Usable only at 640x480@ <
2662306a36Sopenharmony_ci *		     15 fps or 320x240 / 160x120. Note currently the driver
2762306a36Sopenharmony_ci *		     does not use this as the quality gain is small and the
2862306a36Sopenharmony_ci *		     generated JPG-s are only understood by v4l-utils >= 0.8.9
2962306a36Sopenharmony_ci *		0x3f From usb captures under Windows for 320x240
3062306a36Sopenharmony_ci *		0x69 From usb captures under Windows for 160x120
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * Register page 4:
3362306a36Sopenharmony_ci *
3462306a36Sopenharmony_ci * Address	Description
3562306a36Sopenharmony_ci * 0x02		Clock divider 2-63, fps =~ 60 / val. Must be a multiple of 3 on
3662306a36Sopenharmony_ci *		the 7302, so one of 3, 6, 9, ..., except when between 6 and 12?
3762306a36Sopenharmony_ci * 0x0f		Master gain 1-245, low value = high gain
3862306a36Sopenharmony_ci * 0x10		Another gain 0-15, limited influence (1-2x gain I guess)
3962306a36Sopenharmony_ci * 0x21		Bitfield: 0-1 unused, 2-3 vflip/hflip, 4-5 unknown, 6-7 unused
4062306a36Sopenharmony_ci *		Note setting vflip disabled leads to a much lower image quality,
4162306a36Sopenharmony_ci *		so we always vflip, and tell userspace to flip it back
4262306a36Sopenharmony_ci * 0x27		Seems to toggle various gains on / off, Setting bit 7 seems to
4362306a36Sopenharmony_ci *		completely disable the analog amplification block. Set to 0x68
4462306a36Sopenharmony_ci *		for max gain, 0x14 for minimal gain.
4562306a36Sopenharmony_ci */
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define MODULE_NAME "pac7311"
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci#include <linux/input.h>
5262306a36Sopenharmony_ci#include "gspca.h"
5362306a36Sopenharmony_ci/* Include pac common sof detection functions */
5462306a36Sopenharmony_ci#include "pac_common.h"
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci#define PAC7311_GAIN_DEFAULT     122
5762306a36Sopenharmony_ci#define PAC7311_EXPOSURE_DEFAULT   3 /* 20 fps, avoid using high compr. */
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ciMODULE_AUTHOR("Thomas Kaiser thomas@kaiser-linux.li");
6062306a36Sopenharmony_ciMODULE_DESCRIPTION("Pixart PAC7311");
6162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistruct sd {
6462306a36Sopenharmony_ci	struct gspca_dev gspca_dev;		/* !! must be the first item */
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	struct v4l2_ctrl *contrast;
6762306a36Sopenharmony_ci	struct v4l2_ctrl *hflip;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	u8 sof_read;
7062306a36Sopenharmony_ci	u8 autogain_ignore_frames;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	atomic_t avg_lum;
7362306a36Sopenharmony_ci};
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic const struct v4l2_pix_format vga_mode[] = {
7662306a36Sopenharmony_ci	{160, 120, V4L2_PIX_FMT_PJPG, V4L2_FIELD_NONE,
7762306a36Sopenharmony_ci		.bytesperline = 160,
7862306a36Sopenharmony_ci		.sizeimage = 160 * 120 * 3 / 8 + 590,
7962306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_JPEG,
8062306a36Sopenharmony_ci		.priv = 2},
8162306a36Sopenharmony_ci	{320, 240, V4L2_PIX_FMT_PJPG, V4L2_FIELD_NONE,
8262306a36Sopenharmony_ci		.bytesperline = 320,
8362306a36Sopenharmony_ci		.sizeimage = 320 * 240 * 3 / 8 + 590,
8462306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_JPEG,
8562306a36Sopenharmony_ci		.priv = 1},
8662306a36Sopenharmony_ci	{640, 480, V4L2_PIX_FMT_PJPG, V4L2_FIELD_NONE,
8762306a36Sopenharmony_ci		.bytesperline = 640,
8862306a36Sopenharmony_ci		.sizeimage = 640 * 480 * 3 / 8 + 590,
8962306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_JPEG,
9062306a36Sopenharmony_ci		.priv = 0},
9162306a36Sopenharmony_ci};
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci#define LOAD_PAGE4		254
9462306a36Sopenharmony_ci#define END_OF_SEQUENCE		0
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic const __u8 init_7311[] = {
9762306a36Sopenharmony_ci	0xff, 0x01,
9862306a36Sopenharmony_ci	0x78, 0x40,	/* Bit_0=start stream, Bit_6=LED */
9962306a36Sopenharmony_ci	0x78, 0x40,	/* Bit_0=start stream, Bit_6=LED */
10062306a36Sopenharmony_ci	0x78, 0x44,	/* Bit_0=start stream, Bit_6=LED */
10162306a36Sopenharmony_ci	0xff, 0x04,
10262306a36Sopenharmony_ci	0x27, 0x80,
10362306a36Sopenharmony_ci	0x28, 0xca,
10462306a36Sopenharmony_ci	0x29, 0x53,
10562306a36Sopenharmony_ci	0x2a, 0x0e,
10662306a36Sopenharmony_ci	0xff, 0x01,
10762306a36Sopenharmony_ci	0x3e, 0x20,
10862306a36Sopenharmony_ci};
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic const __u8 start_7311[] = {
11162306a36Sopenharmony_ci/*	index, len, [value]* */
11262306a36Sopenharmony_ci	0xff, 1,	0x01,		/* page 1 */
11362306a36Sopenharmony_ci	0x02, 43,	0x48, 0x0a, 0x40, 0x08, 0x00, 0x00, 0x08, 0x00,
11462306a36Sopenharmony_ci			0x06, 0xff, 0x11, 0xff, 0x5a, 0x30, 0x90, 0x4c,
11562306a36Sopenharmony_ci			0x00, 0x07, 0x00, 0x0a, 0x10, 0x00, 0xa0, 0x10,
11662306a36Sopenharmony_ci			0x02, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x00,
11762306a36Sopenharmony_ci			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
11862306a36Sopenharmony_ci			0x00, 0x00, 0x00,
11962306a36Sopenharmony_ci	0x3e, 42,	0x00, 0x00, 0x78, 0x52, 0x4a, 0x52, 0x78, 0x6e,
12062306a36Sopenharmony_ci			0x48, 0x46, 0x48, 0x6e, 0x5f, 0x49, 0x42, 0x49,
12162306a36Sopenharmony_ci			0x5f, 0x5f, 0x49, 0x42, 0x49, 0x5f, 0x6e, 0x48,
12262306a36Sopenharmony_ci			0x46, 0x48, 0x6e, 0x78, 0x52, 0x4a, 0x52, 0x78,
12362306a36Sopenharmony_ci			0x00, 0x00, 0x09, 0x1b, 0x34, 0x49, 0x5c, 0x9b,
12462306a36Sopenharmony_ci			0xd0, 0xff,
12562306a36Sopenharmony_ci	0x78, 6,	0x44, 0x00, 0xf2, 0x01, 0x01, 0x80,
12662306a36Sopenharmony_ci	0x7f, 18,	0x2a, 0x1c, 0x00, 0xc8, 0x02, 0x58, 0x03, 0x84,
12762306a36Sopenharmony_ci			0x12, 0x00, 0x1a, 0x04, 0x08, 0x0c, 0x10, 0x14,
12862306a36Sopenharmony_ci			0x18, 0x20,
12962306a36Sopenharmony_ci	0x96, 3,	0x01, 0x08, 0x04,
13062306a36Sopenharmony_ci	0xa0, 4,	0x44, 0x44, 0x44, 0x04,
13162306a36Sopenharmony_ci	0xf0, 13,	0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x20, 0x00,
13262306a36Sopenharmony_ci			0x3f, 0x00, 0x0a, 0x01, 0x00,
13362306a36Sopenharmony_ci	0xff, 1,	0x04,		/* page 4 */
13462306a36Sopenharmony_ci	0, LOAD_PAGE4,			/* load the page 4 */
13562306a36Sopenharmony_ci	0x11, 1,	0x01,
13662306a36Sopenharmony_ci	0, END_OF_SEQUENCE		/* end of sequence */
13762306a36Sopenharmony_ci};
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci#define SKIP		0xaa
14062306a36Sopenharmony_ci/* page 4 - the value SKIP says skip the index - see reg_w_page() */
14162306a36Sopenharmony_cistatic const __u8 page4_7311[] = {
14262306a36Sopenharmony_ci	SKIP, SKIP, 0x04, 0x54, 0x07, 0x2b, 0x09, 0x0f,
14362306a36Sopenharmony_ci	0x09, 0x00, SKIP, SKIP, 0x07, 0x00, 0x00, 0x62,
14462306a36Sopenharmony_ci	0x08, SKIP, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
14562306a36Sopenharmony_ci	0x00, 0x00, 0x00, 0x03, 0xa0, 0x01, 0xf4, SKIP,
14662306a36Sopenharmony_ci	SKIP, 0x00, 0x08, SKIP, 0x03, SKIP, 0x00, 0x68,
14762306a36Sopenharmony_ci	0xca, 0x10, 0x06, 0x78, 0x00, 0x00, 0x00, 0x00,
14862306a36Sopenharmony_ci	0x23, 0x28, 0x04, 0x11, 0x00, 0x00
14962306a36Sopenharmony_ci};
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic void reg_w_buf(struct gspca_dev *gspca_dev,
15262306a36Sopenharmony_ci		  __u8 index,
15362306a36Sopenharmony_ci		  const u8 *buffer, int len)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	int ret;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (gspca_dev->usb_err < 0)
15862306a36Sopenharmony_ci		return;
15962306a36Sopenharmony_ci	memcpy(gspca_dev->usb_buf, buffer, len);
16062306a36Sopenharmony_ci	ret = usb_control_msg(gspca_dev->dev,
16162306a36Sopenharmony_ci			usb_sndctrlpipe(gspca_dev->dev, 0),
16262306a36Sopenharmony_ci			0,		/* request */
16362306a36Sopenharmony_ci			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
16462306a36Sopenharmony_ci			0,		/* value */
16562306a36Sopenharmony_ci			index, gspca_dev->usb_buf, len,
16662306a36Sopenharmony_ci			500);
16762306a36Sopenharmony_ci	if (ret < 0) {
16862306a36Sopenharmony_ci		pr_err("reg_w_buf() failed index 0x%02x, error %d\n",
16962306a36Sopenharmony_ci		       index, ret);
17062306a36Sopenharmony_ci		gspca_dev->usb_err = ret;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic void reg_w(struct gspca_dev *gspca_dev,
17662306a36Sopenharmony_ci		  __u8 index,
17762306a36Sopenharmony_ci		  __u8 value)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	int ret;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (gspca_dev->usb_err < 0)
18262306a36Sopenharmony_ci		return;
18362306a36Sopenharmony_ci	gspca_dev->usb_buf[0] = value;
18462306a36Sopenharmony_ci	ret = usb_control_msg(gspca_dev->dev,
18562306a36Sopenharmony_ci			usb_sndctrlpipe(gspca_dev->dev, 0),
18662306a36Sopenharmony_ci			0,			/* request */
18762306a36Sopenharmony_ci			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
18862306a36Sopenharmony_ci			0, index, gspca_dev->usb_buf, 1,
18962306a36Sopenharmony_ci			500);
19062306a36Sopenharmony_ci	if (ret < 0) {
19162306a36Sopenharmony_ci		pr_err("reg_w() failed index 0x%02x, value 0x%02x, error %d\n",
19262306a36Sopenharmony_ci		       index, value, ret);
19362306a36Sopenharmony_ci		gspca_dev->usb_err = ret;
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic void reg_w_seq(struct gspca_dev *gspca_dev,
19862306a36Sopenharmony_ci		const __u8 *seq, int len)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	while (--len >= 0) {
20162306a36Sopenharmony_ci		reg_w(gspca_dev, seq[0], seq[1]);
20262306a36Sopenharmony_ci		seq += 2;
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci/* load the beginning of a page */
20762306a36Sopenharmony_cistatic void reg_w_page(struct gspca_dev *gspca_dev,
20862306a36Sopenharmony_ci			const __u8 *page, int len)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	int index;
21162306a36Sopenharmony_ci	int ret = 0;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (gspca_dev->usb_err < 0)
21462306a36Sopenharmony_ci		return;
21562306a36Sopenharmony_ci	for (index = 0; index < len; index++) {
21662306a36Sopenharmony_ci		if (page[index] == SKIP)		/* skip this index */
21762306a36Sopenharmony_ci			continue;
21862306a36Sopenharmony_ci		gspca_dev->usb_buf[0] = page[index];
21962306a36Sopenharmony_ci		ret = usb_control_msg(gspca_dev->dev,
22062306a36Sopenharmony_ci				usb_sndctrlpipe(gspca_dev->dev, 0),
22162306a36Sopenharmony_ci				0,			/* request */
22262306a36Sopenharmony_ci			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
22362306a36Sopenharmony_ci				0, index, gspca_dev->usb_buf, 1,
22462306a36Sopenharmony_ci				500);
22562306a36Sopenharmony_ci		if (ret < 0) {
22662306a36Sopenharmony_ci			pr_err("reg_w_page() failed index 0x%02x, value 0x%02x, error %d\n",
22762306a36Sopenharmony_ci			       index, page[index], ret);
22862306a36Sopenharmony_ci			gspca_dev->usb_err = ret;
22962306a36Sopenharmony_ci			break;
23062306a36Sopenharmony_ci		}
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci/* output a variable sequence */
23562306a36Sopenharmony_cistatic void reg_w_var(struct gspca_dev *gspca_dev,
23662306a36Sopenharmony_ci			const __u8 *seq,
23762306a36Sopenharmony_ci			const __u8 *page4, unsigned int page4_len)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	int index, len;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	for (;;) {
24262306a36Sopenharmony_ci		index = *seq++;
24362306a36Sopenharmony_ci		len = *seq++;
24462306a36Sopenharmony_ci		switch (len) {
24562306a36Sopenharmony_ci		case END_OF_SEQUENCE:
24662306a36Sopenharmony_ci			return;
24762306a36Sopenharmony_ci		case LOAD_PAGE4:
24862306a36Sopenharmony_ci			reg_w_page(gspca_dev, page4, page4_len);
24962306a36Sopenharmony_ci			break;
25062306a36Sopenharmony_ci		default:
25162306a36Sopenharmony_ci			if (len > USB_BUF_SZ) {
25262306a36Sopenharmony_ci				gspca_err(gspca_dev, "Incorrect variable sequence\n");
25362306a36Sopenharmony_ci				return;
25462306a36Sopenharmony_ci			}
25562306a36Sopenharmony_ci			while (len > 0) {
25662306a36Sopenharmony_ci				if (len < 8) {
25762306a36Sopenharmony_ci					reg_w_buf(gspca_dev,
25862306a36Sopenharmony_ci						index, seq, len);
25962306a36Sopenharmony_ci					seq += len;
26062306a36Sopenharmony_ci					break;
26162306a36Sopenharmony_ci				}
26262306a36Sopenharmony_ci				reg_w_buf(gspca_dev, index, seq, 8);
26362306a36Sopenharmony_ci				seq += 8;
26462306a36Sopenharmony_ci				index += 8;
26562306a36Sopenharmony_ci				len -= 8;
26662306a36Sopenharmony_ci			}
26762306a36Sopenharmony_ci		}
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci	/* not reached */
27062306a36Sopenharmony_ci}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci/* this function is called at probe time for pac7311 */
27362306a36Sopenharmony_cistatic int sd_config(struct gspca_dev *gspca_dev,
27462306a36Sopenharmony_ci			const struct usb_device_id *id)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct cam *cam = &gspca_dev->cam;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	cam->cam_mode = vga_mode;
27962306a36Sopenharmony_ci	cam->nmodes = ARRAY_SIZE(vga_mode);
28062306a36Sopenharmony_ci	cam->input_flags = V4L2_IN_ST_VFLIP;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	return 0;
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_cistatic void setcontrast(struct gspca_dev *gspca_dev, s32 val)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	reg_w(gspca_dev, 0xff, 0x04);
28862306a36Sopenharmony_ci	reg_w(gspca_dev, 0x10, val);
28962306a36Sopenharmony_ci	/* load registers to sensor (Bit 0, auto clear) */
29062306a36Sopenharmony_ci	reg_w(gspca_dev, 0x11, 0x01);
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic void setgain(struct gspca_dev *gspca_dev, s32 val)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	reg_w(gspca_dev, 0xff, 0x04);			/* page 4 */
29662306a36Sopenharmony_ci	reg_w(gspca_dev, 0x0e, 0x00);
29762306a36Sopenharmony_ci	reg_w(gspca_dev, 0x0f, gspca_dev->gain->maximum - val + 1);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	/* load registers to sensor (Bit 0, auto clear) */
30062306a36Sopenharmony_ci	reg_w(gspca_dev, 0x11, 0x01);
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic void setexposure(struct gspca_dev *gspca_dev, s32 val)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	reg_w(gspca_dev, 0xff, 0x04);			/* page 4 */
30662306a36Sopenharmony_ci	reg_w(gspca_dev, 0x02, val);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	/* load registers to sensor (Bit 0, auto clear) */
30962306a36Sopenharmony_ci	reg_w(gspca_dev, 0x11, 0x01);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	/*
31262306a36Sopenharmony_ci	 * Page 1 register 8 must always be 0x08 except when not in
31362306a36Sopenharmony_ci	 *  640x480 mode and page 4 reg 2 <= 3 then it must be 9
31462306a36Sopenharmony_ci	 */
31562306a36Sopenharmony_ci	reg_w(gspca_dev, 0xff, 0x01);
31662306a36Sopenharmony_ci	if (gspca_dev->pixfmt.width != 640 && val <= 3)
31762306a36Sopenharmony_ci		reg_w(gspca_dev, 0x08, 0x09);
31862306a36Sopenharmony_ci	else
31962306a36Sopenharmony_ci		reg_w(gspca_dev, 0x08, 0x08);
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	/*
32262306a36Sopenharmony_ci	 * Page1 register 80 sets the compression balance, normally we
32362306a36Sopenharmony_ci	 * want / use 0x1c, but for 640x480@30fps we must allow the
32462306a36Sopenharmony_ci	 * camera to use higher compression or we may run out of
32562306a36Sopenharmony_ci	 * bandwidth.
32662306a36Sopenharmony_ci	 */
32762306a36Sopenharmony_ci	if (gspca_dev->pixfmt.width == 640 && val == 2)
32862306a36Sopenharmony_ci		reg_w(gspca_dev, 0x80, 0x01);
32962306a36Sopenharmony_ci	else
33062306a36Sopenharmony_ci		reg_w(gspca_dev, 0x80, 0x1c);
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	/* load registers to sensor (Bit 0, auto clear) */
33362306a36Sopenharmony_ci	reg_w(gspca_dev, 0x11, 0x01);
33462306a36Sopenharmony_ci}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_cistatic void sethvflip(struct gspca_dev *gspca_dev, s32 hflip, s32 vflip)
33762306a36Sopenharmony_ci{
33862306a36Sopenharmony_ci	__u8 data;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	reg_w(gspca_dev, 0xff, 0x04);			/* page 4 */
34162306a36Sopenharmony_ci	data = (hflip ? 0x04 : 0x00) |
34262306a36Sopenharmony_ci	       (vflip ? 0x08 : 0x00);
34362306a36Sopenharmony_ci	reg_w(gspca_dev, 0x21, data);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/* load registers to sensor (Bit 0, auto clear) */
34662306a36Sopenharmony_ci	reg_w(gspca_dev, 0x11, 0x01);
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci/* this function is called at probe and resume time for pac7311 */
35062306a36Sopenharmony_cistatic int sd_init(struct gspca_dev *gspca_dev)
35162306a36Sopenharmony_ci{
35262306a36Sopenharmony_ci	reg_w_seq(gspca_dev, init_7311, sizeof(init_7311)/2);
35362306a36Sopenharmony_ci	return gspca_dev->usb_err;
35462306a36Sopenharmony_ci}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_cistatic int sd_s_ctrl(struct v4l2_ctrl *ctrl)
35762306a36Sopenharmony_ci{
35862306a36Sopenharmony_ci	struct gspca_dev *gspca_dev =
35962306a36Sopenharmony_ci		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
36062306a36Sopenharmony_ci	struct sd *sd = (struct sd *)gspca_dev;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	gspca_dev->usb_err = 0;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	if (ctrl->id == V4L2_CID_AUTOGAIN && ctrl->is_new && ctrl->val) {
36562306a36Sopenharmony_ci		/* when switching to autogain set defaults to make sure
36662306a36Sopenharmony_ci		   we are on a valid point of the autogain gain /
36762306a36Sopenharmony_ci		   exposure knee graph, and give this change time to
36862306a36Sopenharmony_ci		   take effect before doing autogain. */
36962306a36Sopenharmony_ci		gspca_dev->exposure->val    = PAC7311_EXPOSURE_DEFAULT;
37062306a36Sopenharmony_ci		gspca_dev->gain->val        = PAC7311_GAIN_DEFAULT;
37162306a36Sopenharmony_ci		sd->autogain_ignore_frames  = PAC_AUTOGAIN_IGNORE_FRAMES;
37262306a36Sopenharmony_ci	}
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	if (!gspca_dev->streaming)
37562306a36Sopenharmony_ci		return 0;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	switch (ctrl->id) {
37862306a36Sopenharmony_ci	case V4L2_CID_CONTRAST:
37962306a36Sopenharmony_ci		setcontrast(gspca_dev, ctrl->val);
38062306a36Sopenharmony_ci		break;
38162306a36Sopenharmony_ci	case V4L2_CID_AUTOGAIN:
38262306a36Sopenharmony_ci		if (gspca_dev->exposure->is_new || (ctrl->is_new && ctrl->val))
38362306a36Sopenharmony_ci			setexposure(gspca_dev, gspca_dev->exposure->val);
38462306a36Sopenharmony_ci		if (gspca_dev->gain->is_new || (ctrl->is_new && ctrl->val))
38562306a36Sopenharmony_ci			setgain(gspca_dev, gspca_dev->gain->val);
38662306a36Sopenharmony_ci		break;
38762306a36Sopenharmony_ci	case V4L2_CID_HFLIP:
38862306a36Sopenharmony_ci		sethvflip(gspca_dev, sd->hflip->val, 1);
38962306a36Sopenharmony_ci		break;
39062306a36Sopenharmony_ci	default:
39162306a36Sopenharmony_ci		return -EINVAL;
39262306a36Sopenharmony_ci	}
39362306a36Sopenharmony_ci	return gspca_dev->usb_err;
39462306a36Sopenharmony_ci}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops sd_ctrl_ops = {
39762306a36Sopenharmony_ci	.s_ctrl = sd_s_ctrl,
39862306a36Sopenharmony_ci};
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci/* this function is called at probe time */
40162306a36Sopenharmony_cistatic int sd_init_controls(struct gspca_dev *gspca_dev)
40262306a36Sopenharmony_ci{
40362306a36Sopenharmony_ci	struct sd *sd = (struct sd *) gspca_dev;
40462306a36Sopenharmony_ci	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	gspca_dev->vdev.ctrl_handler = hdl;
40762306a36Sopenharmony_ci	v4l2_ctrl_handler_init(hdl, 5);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	sd->contrast = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
41062306a36Sopenharmony_ci					V4L2_CID_CONTRAST, 0, 15, 1, 7);
41162306a36Sopenharmony_ci	gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
41262306a36Sopenharmony_ci					V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
41362306a36Sopenharmony_ci	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
41462306a36Sopenharmony_ci					V4L2_CID_EXPOSURE, 2, 63, 1,
41562306a36Sopenharmony_ci					PAC7311_EXPOSURE_DEFAULT);
41662306a36Sopenharmony_ci	gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
41762306a36Sopenharmony_ci					V4L2_CID_GAIN, 0, 244, 1,
41862306a36Sopenharmony_ci					PAC7311_GAIN_DEFAULT);
41962306a36Sopenharmony_ci	sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
42062306a36Sopenharmony_ci		V4L2_CID_HFLIP, 0, 1, 1, 0);
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	if (hdl->error) {
42362306a36Sopenharmony_ci		pr_err("Could not initialize controls\n");
42462306a36Sopenharmony_ci		return hdl->error;
42562306a36Sopenharmony_ci	}
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
42862306a36Sopenharmony_ci	return 0;
42962306a36Sopenharmony_ci}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci/* -- start the camera -- */
43262306a36Sopenharmony_cistatic int sd_start(struct gspca_dev *gspca_dev)
43362306a36Sopenharmony_ci{
43462306a36Sopenharmony_ci	struct sd *sd = (struct sd *) gspca_dev;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	sd->sof_read = 0;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	reg_w_var(gspca_dev, start_7311,
43962306a36Sopenharmony_ci		page4_7311, sizeof(page4_7311));
44062306a36Sopenharmony_ci	setcontrast(gspca_dev, v4l2_ctrl_g_ctrl(sd->contrast));
44162306a36Sopenharmony_ci	setgain(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->gain));
44262306a36Sopenharmony_ci	setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure));
44362306a36Sopenharmony_ci	sethvflip(gspca_dev, v4l2_ctrl_g_ctrl(sd->hflip), 1);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	/* set correct resolution */
44662306a36Sopenharmony_ci	switch (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
44762306a36Sopenharmony_ci	case 2:					/* 160x120 */
44862306a36Sopenharmony_ci		reg_w(gspca_dev, 0xff, 0x01);
44962306a36Sopenharmony_ci		reg_w(gspca_dev, 0x17, 0x20);
45062306a36Sopenharmony_ci		reg_w(gspca_dev, 0x87, 0x10);
45162306a36Sopenharmony_ci		break;
45262306a36Sopenharmony_ci	case 1:					/* 320x240 */
45362306a36Sopenharmony_ci		reg_w(gspca_dev, 0xff, 0x01);
45462306a36Sopenharmony_ci		reg_w(gspca_dev, 0x17, 0x30);
45562306a36Sopenharmony_ci		reg_w(gspca_dev, 0x87, 0x11);
45662306a36Sopenharmony_ci		break;
45762306a36Sopenharmony_ci	case 0:					/* 640x480 */
45862306a36Sopenharmony_ci		reg_w(gspca_dev, 0xff, 0x01);
45962306a36Sopenharmony_ci		reg_w(gspca_dev, 0x17, 0x00);
46062306a36Sopenharmony_ci		reg_w(gspca_dev, 0x87, 0x12);
46162306a36Sopenharmony_ci		break;
46262306a36Sopenharmony_ci	}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	sd->sof_read = 0;
46562306a36Sopenharmony_ci	sd->autogain_ignore_frames = 0;
46662306a36Sopenharmony_ci	atomic_set(&sd->avg_lum, -1);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	/* start stream */
46962306a36Sopenharmony_ci	reg_w(gspca_dev, 0xff, 0x01);
47062306a36Sopenharmony_ci	reg_w(gspca_dev, 0x78, 0x05);
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	return gspca_dev->usb_err;
47362306a36Sopenharmony_ci}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_cistatic void sd_stopN(struct gspca_dev *gspca_dev)
47662306a36Sopenharmony_ci{
47762306a36Sopenharmony_ci	reg_w(gspca_dev, 0xff, 0x04);
47862306a36Sopenharmony_ci	reg_w(gspca_dev, 0x27, 0x80);
47962306a36Sopenharmony_ci	reg_w(gspca_dev, 0x28, 0xca);
48062306a36Sopenharmony_ci	reg_w(gspca_dev, 0x29, 0x53);
48162306a36Sopenharmony_ci	reg_w(gspca_dev, 0x2a, 0x0e);
48262306a36Sopenharmony_ci	reg_w(gspca_dev, 0xff, 0x01);
48362306a36Sopenharmony_ci	reg_w(gspca_dev, 0x3e, 0x20);
48462306a36Sopenharmony_ci	reg_w(gspca_dev, 0x78, 0x44); /* Bit_0=start stream, Bit_6=LED */
48562306a36Sopenharmony_ci	reg_w(gspca_dev, 0x78, 0x44); /* Bit_0=start stream, Bit_6=LED */
48662306a36Sopenharmony_ci	reg_w(gspca_dev, 0x78, 0x44); /* Bit_0=start stream, Bit_6=LED */
48762306a36Sopenharmony_ci}
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_cistatic void do_autogain(struct gspca_dev *gspca_dev)
49062306a36Sopenharmony_ci{
49162306a36Sopenharmony_ci	struct sd *sd = (struct sd *) gspca_dev;
49262306a36Sopenharmony_ci	int avg_lum = atomic_read(&sd->avg_lum);
49362306a36Sopenharmony_ci	int desired_lum, deadzone;
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	if (avg_lum == -1)
49662306a36Sopenharmony_ci		return;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	desired_lum = 170;
49962306a36Sopenharmony_ci	deadzone = 20;
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	if (sd->autogain_ignore_frames > 0)
50262306a36Sopenharmony_ci		sd->autogain_ignore_frames--;
50362306a36Sopenharmony_ci	else if (gspca_coarse_grained_expo_autogain(gspca_dev, avg_lum,
50462306a36Sopenharmony_ci						    desired_lum, deadzone))
50562306a36Sopenharmony_ci		sd->autogain_ignore_frames = PAC_AUTOGAIN_IGNORE_FRAMES;
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci/* JPEG header, part 1 */
50962306a36Sopenharmony_cistatic const unsigned char pac_jpeg_header1[] = {
51062306a36Sopenharmony_ci  0xff, 0xd8,		/* SOI: Start of Image */
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci  0xff, 0xc0,		/* SOF0: Start of Frame (Baseline DCT) */
51362306a36Sopenharmony_ci  0x00, 0x11,		/* length = 17 bytes (including this length field) */
51462306a36Sopenharmony_ci  0x08			/* Precision: 8 */
51562306a36Sopenharmony_ci  /* 2 bytes is placed here: number of image lines */
51662306a36Sopenharmony_ci  /* 2 bytes is placed here: samples per line */
51762306a36Sopenharmony_ci};
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci/* JPEG header, continued */
52062306a36Sopenharmony_cistatic const unsigned char pac_jpeg_header2[] = {
52162306a36Sopenharmony_ci  0x03,			/* Number of image components: 3 */
52262306a36Sopenharmony_ci  0x01, 0x21, 0x00,	/* ID=1, Subsampling 1x1, Quantization table: 0 */
52362306a36Sopenharmony_ci  0x02, 0x11, 0x01,	/* ID=2, Subsampling 2x1, Quantization table: 1 */
52462306a36Sopenharmony_ci  0x03, 0x11, 0x01,	/* ID=3, Subsampling 2x1, Quantization table: 1 */
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci  0xff, 0xda,		/* SOS: Start Of Scan */
52762306a36Sopenharmony_ci  0x00, 0x0c,		/* length = 12 bytes (including this length field) */
52862306a36Sopenharmony_ci  0x03,			/* number of components: 3 */
52962306a36Sopenharmony_ci  0x01, 0x00,		/* selector 1, table 0x00 */
53062306a36Sopenharmony_ci  0x02, 0x11,		/* selector 2, table 0x11 */
53162306a36Sopenharmony_ci  0x03, 0x11,		/* selector 3, table 0x11 */
53262306a36Sopenharmony_ci  0x00, 0x3f,		/* Spectral selection: 0 .. 63 */
53362306a36Sopenharmony_ci  0x00			/* Successive approximation: 0 */
53462306a36Sopenharmony_ci};
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_cistatic void pac_start_frame(struct gspca_dev *gspca_dev,
53762306a36Sopenharmony_ci		__u16 lines, __u16 samples_per_line)
53862306a36Sopenharmony_ci{
53962306a36Sopenharmony_ci	unsigned char tmpbuf[4];
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	gspca_frame_add(gspca_dev, FIRST_PACKET,
54262306a36Sopenharmony_ci		pac_jpeg_header1, sizeof(pac_jpeg_header1));
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	tmpbuf[0] = lines >> 8;
54562306a36Sopenharmony_ci	tmpbuf[1] = lines & 0xff;
54662306a36Sopenharmony_ci	tmpbuf[2] = samples_per_line >> 8;
54762306a36Sopenharmony_ci	tmpbuf[3] = samples_per_line & 0xff;
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci	gspca_frame_add(gspca_dev, INTER_PACKET,
55062306a36Sopenharmony_ci		tmpbuf, sizeof(tmpbuf));
55162306a36Sopenharmony_ci	gspca_frame_add(gspca_dev, INTER_PACKET,
55262306a36Sopenharmony_ci		pac_jpeg_header2, sizeof(pac_jpeg_header2));
55362306a36Sopenharmony_ci}
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci/* this function is run at interrupt level */
55662306a36Sopenharmony_cistatic void sd_pkt_scan(struct gspca_dev *gspca_dev,
55762306a36Sopenharmony_ci			u8 *data,			/* isoc packet */
55862306a36Sopenharmony_ci			int len)			/* iso packet length */
55962306a36Sopenharmony_ci{
56062306a36Sopenharmony_ci	struct sd *sd = (struct sd *) gspca_dev;
56162306a36Sopenharmony_ci	u8 *image;
56262306a36Sopenharmony_ci	unsigned char *sof;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	sof = pac_find_sof(gspca_dev, &sd->sof_read, data, len);
56562306a36Sopenharmony_ci	if (sof) {
56662306a36Sopenharmony_ci		int n, lum_offset, footer_length;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci		/*
56962306a36Sopenharmony_ci		 * 6 bytes after the FF D9 EOF marker a number of lumination
57062306a36Sopenharmony_ci		 * bytes are send corresponding to different parts of the
57162306a36Sopenharmony_ci		 * image, the 14th and 15th byte after the EOF seem to
57262306a36Sopenharmony_ci		 * correspond to the center of the image.
57362306a36Sopenharmony_ci		 */
57462306a36Sopenharmony_ci		lum_offset = 24 + sizeof pac_sof_marker;
57562306a36Sopenharmony_ci		footer_length = 26;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci		/* Finish decoding current frame */
57862306a36Sopenharmony_ci		n = (sof - data) - (footer_length + sizeof pac_sof_marker);
57962306a36Sopenharmony_ci		if (n < 0) {
58062306a36Sopenharmony_ci			gspca_dev->image_len += n;
58162306a36Sopenharmony_ci			n = 0;
58262306a36Sopenharmony_ci		} else {
58362306a36Sopenharmony_ci			gspca_frame_add(gspca_dev, INTER_PACKET, data, n);
58462306a36Sopenharmony_ci		}
58562306a36Sopenharmony_ci		image = gspca_dev->image;
58662306a36Sopenharmony_ci		if (image != NULL
58762306a36Sopenharmony_ci		 && image[gspca_dev->image_len - 2] == 0xff
58862306a36Sopenharmony_ci		 && image[gspca_dev->image_len - 1] == 0xd9)
58962306a36Sopenharmony_ci			gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci		n = sof - data;
59262306a36Sopenharmony_ci		len -= n;
59362306a36Sopenharmony_ci		data = sof;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci		/* Get average lumination */
59662306a36Sopenharmony_ci		if (gspca_dev->last_packet_type == LAST_PACKET &&
59762306a36Sopenharmony_ci				n >= lum_offset)
59862306a36Sopenharmony_ci			atomic_set(&sd->avg_lum, data[-lum_offset] +
59962306a36Sopenharmony_ci						data[-lum_offset + 1]);
60062306a36Sopenharmony_ci		else
60162306a36Sopenharmony_ci			atomic_set(&sd->avg_lum, -1);
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci		/* Start the new frame with the jpeg header */
60462306a36Sopenharmony_ci		pac_start_frame(gspca_dev,
60562306a36Sopenharmony_ci			gspca_dev->pixfmt.height, gspca_dev->pixfmt.width);
60662306a36Sopenharmony_ci	}
60762306a36Sopenharmony_ci	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
60862306a36Sopenharmony_ci}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_INPUT)
61162306a36Sopenharmony_cistatic int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
61262306a36Sopenharmony_ci			u8 *data,		/* interrupt packet data */
61362306a36Sopenharmony_ci			int len)		/* interrupt packet length */
61462306a36Sopenharmony_ci{
61562306a36Sopenharmony_ci	int ret = -EINVAL;
61662306a36Sopenharmony_ci	u8 data0, data1;
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	if (len == 2) {
61962306a36Sopenharmony_ci		data0 = data[0];
62062306a36Sopenharmony_ci		data1 = data[1];
62162306a36Sopenharmony_ci		if ((data0 == 0x00 && data1 == 0x11) ||
62262306a36Sopenharmony_ci		    (data0 == 0x22 && data1 == 0x33) ||
62362306a36Sopenharmony_ci		    (data0 == 0x44 && data1 == 0x55) ||
62462306a36Sopenharmony_ci		    (data0 == 0x66 && data1 == 0x77) ||
62562306a36Sopenharmony_ci		    (data0 == 0x88 && data1 == 0x99) ||
62662306a36Sopenharmony_ci		    (data0 == 0xaa && data1 == 0xbb) ||
62762306a36Sopenharmony_ci		    (data0 == 0xcc && data1 == 0xdd) ||
62862306a36Sopenharmony_ci		    (data0 == 0xee && data1 == 0xff)) {
62962306a36Sopenharmony_ci			input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
63062306a36Sopenharmony_ci			input_sync(gspca_dev->input_dev);
63162306a36Sopenharmony_ci			input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
63262306a36Sopenharmony_ci			input_sync(gspca_dev->input_dev);
63362306a36Sopenharmony_ci			ret = 0;
63462306a36Sopenharmony_ci		}
63562306a36Sopenharmony_ci	}
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	return ret;
63862306a36Sopenharmony_ci}
63962306a36Sopenharmony_ci#endif
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_cistatic const struct sd_desc sd_desc = {
64262306a36Sopenharmony_ci	.name = MODULE_NAME,
64362306a36Sopenharmony_ci	.config = sd_config,
64462306a36Sopenharmony_ci	.init = sd_init,
64562306a36Sopenharmony_ci	.init_controls = sd_init_controls,
64662306a36Sopenharmony_ci	.start = sd_start,
64762306a36Sopenharmony_ci	.stopN = sd_stopN,
64862306a36Sopenharmony_ci	.pkt_scan = sd_pkt_scan,
64962306a36Sopenharmony_ci	.dq_callback = do_autogain,
65062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_INPUT)
65162306a36Sopenharmony_ci	.int_pkt_scan = sd_int_pkt_scan,
65262306a36Sopenharmony_ci#endif
65362306a36Sopenharmony_ci};
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci/* -- module initialisation -- */
65662306a36Sopenharmony_cistatic const struct usb_device_id device_table[] = {
65762306a36Sopenharmony_ci	{USB_DEVICE(0x093a, 0x2600)},
65862306a36Sopenharmony_ci	{USB_DEVICE(0x093a, 0x2601)},
65962306a36Sopenharmony_ci	{USB_DEVICE(0x093a, 0x2603)},
66062306a36Sopenharmony_ci	{USB_DEVICE(0x093a, 0x2608)},
66162306a36Sopenharmony_ci	{USB_DEVICE(0x093a, 0x260e)},
66262306a36Sopenharmony_ci	{USB_DEVICE(0x093a, 0x260f)},
66362306a36Sopenharmony_ci	{}
66462306a36Sopenharmony_ci};
66562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, device_table);
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci/* -- device connect -- */
66862306a36Sopenharmony_cistatic int sd_probe(struct usb_interface *intf,
66962306a36Sopenharmony_ci			const struct usb_device_id *id)
67062306a36Sopenharmony_ci{
67162306a36Sopenharmony_ci	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
67262306a36Sopenharmony_ci				THIS_MODULE);
67362306a36Sopenharmony_ci}
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_cistatic struct usb_driver sd_driver = {
67662306a36Sopenharmony_ci	.name = MODULE_NAME,
67762306a36Sopenharmony_ci	.id_table = device_table,
67862306a36Sopenharmony_ci	.probe = sd_probe,
67962306a36Sopenharmony_ci	.disconnect = gspca_disconnect,
68062306a36Sopenharmony_ci#ifdef CONFIG_PM
68162306a36Sopenharmony_ci	.suspend = gspca_suspend,
68262306a36Sopenharmony_ci	.resume = gspca_resume,
68362306a36Sopenharmony_ci	.reset_resume = gspca_resume,
68462306a36Sopenharmony_ci#endif
68562306a36Sopenharmony_ci};
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_cimodule_usb_driver(sd_driver);
688