162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ToupTek UCMOS / AmScope MU series camera driver
462306a36Sopenharmony_ci * TODO: contrast with ScopeTek / AmScope MDC cameras
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2012-2014 John McMaster <JohnDMcMaster@gmail.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Special thanks to Bushing for helping with the decrypt algorithm and
962306a36Sopenharmony_ci * Sean O'Sullivan / the Rensselaer Center for Open Source
1062306a36Sopenharmony_ci * Software (RCOS) for helping me learn kernel development
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "gspca.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define MODULE_NAME "touptek"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ciMODULE_AUTHOR("John McMaster");
1862306a36Sopenharmony_ciMODULE_DESCRIPTION("ToupTek UCMOS / Amscope MU microscope camera driver");
1962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci * Exposure reg is linear with exposure time
2362306a36Sopenharmony_ci * Exposure (sec), E (reg)
2462306a36Sopenharmony_ci * 0.000400, 0x0002
2562306a36Sopenharmony_ci * 0.001000, 0x0005
2662306a36Sopenharmony_ci * 0.005000, 0x0019
2762306a36Sopenharmony_ci * 0.020000, 0x0064
2862306a36Sopenharmony_ci * 0.080000, 0x0190
2962306a36Sopenharmony_ci * 0.400000, 0x07D0
3062306a36Sopenharmony_ci * 1.000000, 0x1388
3162306a36Sopenharmony_ci * 2.000000, 0x2710
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci * Three gain stages
3462306a36Sopenharmony_ci * 0x1000: master channel enable bit
3562306a36Sopenharmony_ci * 0x007F: low gain bits
3662306a36Sopenharmony_ci * 0x0080: medium gain bit
3762306a36Sopenharmony_ci * 0x0100: high gain bit
3862306a36Sopenharmony_ci * gain = enable * (1 + regH) * (1 + regM) * z * regL
3962306a36Sopenharmony_ci *
4062306a36Sopenharmony_ci * Gain implementation
4162306a36Sopenharmony_ci * Want to do something similar to mt9v011.c's set_balance
4262306a36Sopenharmony_ci *
4362306a36Sopenharmony_ci * Gain does not vary with resolution (checked 640x480 vs 1600x1200)
4462306a36Sopenharmony_ci *
4562306a36Sopenharmony_ci * Constant derivation:
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci * Raw data:
4862306a36Sopenharmony_ci * Gain,   GTOP,   B,	  R,	  GBOT
4962306a36Sopenharmony_ci * 1.00,   0x105C, 0x1068, 0x10C8, 0x105C
5062306a36Sopenharmony_ci * 1.20,   0x106E, 0x107E, 0x10D6, 0x106E
5162306a36Sopenharmony_ci * 1.40,   0x10C0, 0x10CA, 0x10E5, 0x10C0
5262306a36Sopenharmony_ci * 1.60,   0x10C9, 0x10D4, 0x10F3, 0x10C9
5362306a36Sopenharmony_ci * 1.80,   0x10D2, 0x10DE, 0x11C1, 0x10D2
5462306a36Sopenharmony_ci * 2.00,   0x10DC, 0x10E9, 0x11C8, 0x10DC
5562306a36Sopenharmony_ci * 2.20,   0x10E5, 0x10F3, 0x11CF, 0x10E5
5662306a36Sopenharmony_ci * 2.40,   0x10EE, 0x10FE, 0x11D7, 0x10EE
5762306a36Sopenharmony_ci * 2.60,   0x10F7, 0x11C4, 0x11DE, 0x10F7
5862306a36Sopenharmony_ci * 2.80,   0x11C0, 0x11CA, 0x11E5, 0x11C0
5962306a36Sopenharmony_ci * 3.00,   0x11C5, 0x11CF, 0x11ED, 0x11C5
6062306a36Sopenharmony_ci *
6162306a36Sopenharmony_ci * zR = 0.0069605943152454778
6262306a36Sopenharmony_ci *	about 3/431 = 0.0069605568445475635
6362306a36Sopenharmony_ci * zB = 0.0095695970695970703
6462306a36Sopenharmony_ci *	about 6/627 = 0.0095693779904306216
6562306a36Sopenharmony_ci * zG = 0.010889328063241107
6662306a36Sopenharmony_ci *	about 6/551 = 0.010889292196007259
6762306a36Sopenharmony_ci * about 10 bits for constant + 7 bits for value => at least 17 bit
6862306a36Sopenharmony_ci * intermediate with 32 bit ints should be fine for overflow etc
6962306a36Sopenharmony_ci * Essentially gains are in range 0-0x001FF
7062306a36Sopenharmony_ci *
7162306a36Sopenharmony_ci * However, V4L expects a main gain channel + R and B balance
7262306a36Sopenharmony_ci * To keep things simple for now saturate the values of balance is too high/low
7362306a36Sopenharmony_ci * This isn't really ideal but easy way to fit the Linux model
7462306a36Sopenharmony_ci *
7562306a36Sopenharmony_ci * Converted using gain model turns out to be quite linear:
7662306a36Sopenharmony_ci * Gain, GTOP, B, R, GBOT
7762306a36Sopenharmony_ci * 1.00, 92, 104, 144, 92
7862306a36Sopenharmony_ci * 1.20, 110, 126, 172, 110
7962306a36Sopenharmony_ci * 1.40, 128, 148, 202, 128
8062306a36Sopenharmony_ci * 1.60, 146, 168, 230, 146
8162306a36Sopenharmony_ci * 1.80, 164, 188, 260, 164
8262306a36Sopenharmony_ci * 2.00, 184, 210, 288, 184
8362306a36Sopenharmony_ci * 2.20, 202, 230, 316, 202
8462306a36Sopenharmony_ci * 2.40, 220, 252, 348, 220
8562306a36Sopenharmony_ci * 2.60, 238, 272, 376, 238
8662306a36Sopenharmony_ci * 2.80, 256, 296, 404, 256
8762306a36Sopenharmony_ci * 3.00, 276, 316, 436, 276
8862306a36Sopenharmony_ci *
8962306a36Sopenharmony_ci * Maximum gain is 0x7FF * 2 * 2 => 0x1FFC (8188)
9062306a36Sopenharmony_ci * or about 13 effective bits of gain
9162306a36Sopenharmony_ci * The highest the commercial driver goes in my setup 436
9262306a36Sopenharmony_ci * However, because could *maybe* damage circuits
9362306a36Sopenharmony_ci * limit the gain until have a reason to go higher
9462306a36Sopenharmony_ci * Solution: gain clipped and warning emitted
9562306a36Sopenharmony_ci */
9662306a36Sopenharmony_ci#define GAIN_MAX		511
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci/* Frame sync is a short read */
9962306a36Sopenharmony_ci#define BULK_SIZE		0x4000
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci/* MT9E001 reg names to give a rough approximation */
10262306a36Sopenharmony_ci#define REG_COARSE_INTEGRATION_TIME_	0x3012
10362306a36Sopenharmony_ci#define REG_GROUPED_PARAMETER_HOLD_	0x3022
10462306a36Sopenharmony_ci#define REG_MODE_SELECT			0x0100
10562306a36Sopenharmony_ci#define REG_OP_SYS_CLK_DIV		0x030A
10662306a36Sopenharmony_ci#define REG_VT_SYS_CLK_DIV		0x0302
10762306a36Sopenharmony_ci#define REG_PRE_PLL_CLK_DIV		0x0304
10862306a36Sopenharmony_ci#define REG_VT_PIX_CLK_DIV		0x0300
10962306a36Sopenharmony_ci#define REG_OP_PIX_CLK_DIV		0x0308
11062306a36Sopenharmony_ci#define REG_PLL_MULTIPLIER		0x0306
11162306a36Sopenharmony_ci#define REG_COARSE_INTEGRATION_TIME_	0x3012
11262306a36Sopenharmony_ci#define REG_FRAME_LENGTH_LINES		0x0340
11362306a36Sopenharmony_ci#define REG_FRAME_LENGTH_LINES_		0x300A
11462306a36Sopenharmony_ci#define REG_GREEN1_GAIN			0x3056
11562306a36Sopenharmony_ci#define REG_GREEN2_GAIN			0x305C
11662306a36Sopenharmony_ci#define REG_GROUPED_PARAMETER_HOLD	0x0104
11762306a36Sopenharmony_ci#define REG_LINE_LENGTH_PCK_		0x300C
11862306a36Sopenharmony_ci#define REG_MODE_SELECT			0x0100
11962306a36Sopenharmony_ci#define REG_PLL_MULTIPLIER		0x0306
12062306a36Sopenharmony_ci#define REG_READ_MODE			0x3040
12162306a36Sopenharmony_ci#define REG_BLUE_GAIN			0x3058
12262306a36Sopenharmony_ci#define REG_RED_GAIN			0x305A
12362306a36Sopenharmony_ci#define REG_RESET_REGISTER		0x301A
12462306a36Sopenharmony_ci#define REG_SCALE_M			0x0404
12562306a36Sopenharmony_ci#define REG_SCALING_MODE		0x0400
12662306a36Sopenharmony_ci#define REG_SOFTWARE_RESET		0x0103
12762306a36Sopenharmony_ci#define REG_X_ADDR_END			0x0348
12862306a36Sopenharmony_ci#define REG_X_ADDR_START		0x0344
12962306a36Sopenharmony_ci#define REG_X_ADDR_START		0x0344
13062306a36Sopenharmony_ci#define REG_X_OUTPUT_SIZE		0x034C
13162306a36Sopenharmony_ci#define REG_Y_ADDR_END			0x034A
13262306a36Sopenharmony_ci#define REG_Y_ADDR_START		0x0346
13362306a36Sopenharmony_ci#define REG_Y_OUTPUT_SIZE		0x034E
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci/* specific webcam descriptor */
13762306a36Sopenharmony_cistruct sd {
13862306a36Sopenharmony_ci	struct gspca_dev gspca_dev;	/* !! must be the first item */
13962306a36Sopenharmony_ci	/* How many bytes this frame */
14062306a36Sopenharmony_ci	unsigned int this_f;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	/*
14362306a36Sopenharmony_ci	Device has separate gains for each Bayer quadrant
14462306a36Sopenharmony_ci	V4L supports master gain which is referenced to G1/G2 and supplies
14562306a36Sopenharmony_ci	individual balance controls for R/B
14662306a36Sopenharmony_ci	*/
14762306a36Sopenharmony_ci	struct v4l2_ctrl *blue;
14862306a36Sopenharmony_ci	struct v4l2_ctrl *red;
14962306a36Sopenharmony_ci};
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci/* Used to simplify reg write error handling */
15262306a36Sopenharmony_cistruct cmd {
15362306a36Sopenharmony_ci	u16 value;
15462306a36Sopenharmony_ci	u16 index;
15562306a36Sopenharmony_ci};
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic const struct v4l2_pix_format vga_mode[] = {
15862306a36Sopenharmony_ci	{800, 600,
15962306a36Sopenharmony_ci		V4L2_PIX_FMT_SGRBG8,
16062306a36Sopenharmony_ci		V4L2_FIELD_NONE,
16162306a36Sopenharmony_ci		.bytesperline = 800,
16262306a36Sopenharmony_ci		.sizeimage = 800 * 600,
16362306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_SRGB},
16462306a36Sopenharmony_ci	{1600, 1200,
16562306a36Sopenharmony_ci		V4L2_PIX_FMT_SGRBG8,
16662306a36Sopenharmony_ci		V4L2_FIELD_NONE,
16762306a36Sopenharmony_ci		.bytesperline = 1600,
16862306a36Sopenharmony_ci		.sizeimage = 1600 * 1200,
16962306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_SRGB},
17062306a36Sopenharmony_ci	{3264, 2448,
17162306a36Sopenharmony_ci		V4L2_PIX_FMT_SGRBG8,
17262306a36Sopenharmony_ci		V4L2_FIELD_NONE,
17362306a36Sopenharmony_ci		.bytesperline = 3264,
17462306a36Sopenharmony_ci		.sizeimage = 3264 * 2448,
17562306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_SRGB},
17662306a36Sopenharmony_ci};
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci/*
17962306a36Sopenharmony_ci * As there's no known frame sync, the only way to keep synced is to try hard
18062306a36Sopenharmony_ci * to never miss any packets
18162306a36Sopenharmony_ci */
18262306a36Sopenharmony_ci#if MAX_NURBS < 4
18362306a36Sopenharmony_ci#error "Not enough URBs in the gspca table"
18462306a36Sopenharmony_ci#endif
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic int val_reply(struct gspca_dev *gspca_dev, const char *reply, int rc)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	if (rc < 0) {
18962306a36Sopenharmony_ci		gspca_err(gspca_dev, "reply has error %d\n", rc);
19062306a36Sopenharmony_ci		return -EIO;
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci	if (rc != 1) {
19362306a36Sopenharmony_ci		gspca_err(gspca_dev, "Bad reply size %d\n", rc);
19462306a36Sopenharmony_ci		return -EIO;
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci	if (reply[0] != 0x08) {
19762306a36Sopenharmony_ci		gspca_err(gspca_dev, "Bad reply 0x%02x\n", (int)reply[0]);
19862306a36Sopenharmony_ci		return -EIO;
19962306a36Sopenharmony_ci	}
20062306a36Sopenharmony_ci	return 0;
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic void reg_w(struct gspca_dev *gspca_dev, u16 value, u16 index)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	char *buff = gspca_dev->usb_buf;
20662306a36Sopenharmony_ci	int rc;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_USBO,
20962306a36Sopenharmony_ci		  "reg_w bReq=0x0B, bReqT=0xC0, wVal=0x%04X, wInd=0x%04X\n\n",
21062306a36Sopenharmony_ci		  value, index);
21162306a36Sopenharmony_ci	rc = usb_control_msg(gspca_dev->dev, usb_rcvctrlpipe(gspca_dev->dev, 0),
21262306a36Sopenharmony_ci		0x0B, 0xC0, value, index, buff, 1, 500);
21362306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_USBO, "rc=%d, ret={0x%02x}\n", rc, (int)buff[0]);
21462306a36Sopenharmony_ci	if (rc < 0) {
21562306a36Sopenharmony_ci		gspca_err(gspca_dev, "Failed reg_w(0x0B, 0xC0, 0x%04X, 0x%04X) w/ rc %d\n",
21662306a36Sopenharmony_ci			  value, index, rc);
21762306a36Sopenharmony_ci		gspca_dev->usb_err = rc;
21862306a36Sopenharmony_ci		return;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci	if (val_reply(gspca_dev, buff, rc)) {
22162306a36Sopenharmony_ci		gspca_err(gspca_dev, "Bad reply to reg_w(0x0B, 0xC0, 0x%04X, 0x%04X\n",
22262306a36Sopenharmony_ci			  value, index);
22362306a36Sopenharmony_ci		gspca_dev->usb_err = -EIO;
22462306a36Sopenharmony_ci	}
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic void reg_w_buf(struct gspca_dev *gspca_dev,
22862306a36Sopenharmony_ci		const struct cmd *p, int l)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	do {
23162306a36Sopenharmony_ci		reg_w(gspca_dev, p->value, p->index);
23262306a36Sopenharmony_ci		p++;
23362306a36Sopenharmony_ci	} while (--l > 0);
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic void setexposure(struct gspca_dev *gspca_dev, s32 val)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	u16 value;
23962306a36Sopenharmony_ci	unsigned int w = gspca_dev->pixfmt.width;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	if (w == 800)
24262306a36Sopenharmony_ci		value = val * 5;
24362306a36Sopenharmony_ci	else if (w == 1600)
24462306a36Sopenharmony_ci		value = val * 3;
24562306a36Sopenharmony_ci	else if (w == 3264)
24662306a36Sopenharmony_ci		value = val * 3 / 2;
24762306a36Sopenharmony_ci	else {
24862306a36Sopenharmony_ci		gspca_err(gspca_dev, "Invalid width %u\n", w);
24962306a36Sopenharmony_ci		gspca_dev->usb_err = -EINVAL;
25062306a36Sopenharmony_ci		return;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "exposure: 0x%04X ms\n\n", value);
25362306a36Sopenharmony_ci	/* Wonder if there's a good reason for sending it twice */
25462306a36Sopenharmony_ci	/* probably not but leave it in because...why not */
25562306a36Sopenharmony_ci	reg_w(gspca_dev, value, REG_COARSE_INTEGRATION_TIME_);
25662306a36Sopenharmony_ci	reg_w(gspca_dev, value, REG_COARSE_INTEGRATION_TIME_);
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic int gainify(int in)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	/*
26262306a36Sopenharmony_ci	 * TODO: check if there are any issues with corner cases
26362306a36Sopenharmony_ci	 * 0x000 (0):0x07F (127): regL
26462306a36Sopenharmony_ci	 * 0x080 (128) - 0x0FF (255): regM, regL
26562306a36Sopenharmony_ci	 * 0x100 (256) - max: regH, regM, regL
26662306a36Sopenharmony_ci	 */
26762306a36Sopenharmony_ci	if (in <= 0x7F)
26862306a36Sopenharmony_ci		return 0x1000 | in;
26962306a36Sopenharmony_ci	else if (in <= 0xFF)
27062306a36Sopenharmony_ci		return 0x1080 | in / 2;
27162306a36Sopenharmony_ci	else
27262306a36Sopenharmony_ci		return 0x1180 | in / 4;
27362306a36Sopenharmony_ci}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cistatic void setggain(struct gspca_dev *gspca_dev, u16 global_gain)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	u16 normalized;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	normalized = gainify(global_gain);
28062306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "gain G1/G2 (0x%04X): 0x%04X (src 0x%04X)\n\n",
28162306a36Sopenharmony_ci		  REG_GREEN1_GAIN,
28262306a36Sopenharmony_ci		  normalized, global_gain);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	reg_w(gspca_dev, normalized, REG_GREEN1_GAIN);
28562306a36Sopenharmony_ci	reg_w(gspca_dev, normalized, REG_GREEN2_GAIN);
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic void setbgain(struct gspca_dev *gspca_dev,
28962306a36Sopenharmony_ci		u16 gain, u16 global_gain)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	u16 normalized;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	normalized = global_gain +
29462306a36Sopenharmony_ci		((u32)global_gain) * gain / GAIN_MAX;
29562306a36Sopenharmony_ci	if (normalized > GAIN_MAX) {
29662306a36Sopenharmony_ci		gspca_dbg(gspca_dev, D_STREAM, "Truncating blue 0x%04X w/ value 0x%04X\n\n",
29762306a36Sopenharmony_ci			  GAIN_MAX, normalized);
29862306a36Sopenharmony_ci		normalized = GAIN_MAX;
29962306a36Sopenharmony_ci	}
30062306a36Sopenharmony_ci	normalized = gainify(normalized);
30162306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "gain B (0x%04X): 0x%04X w/ source 0x%04X\n\n",
30262306a36Sopenharmony_ci		  REG_BLUE_GAIN, normalized, gain);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	reg_w(gspca_dev, normalized, REG_BLUE_GAIN);
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic void setrgain(struct gspca_dev *gspca_dev,
30862306a36Sopenharmony_ci		u16 gain, u16 global_gain)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	u16 normalized;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	normalized = global_gain +
31362306a36Sopenharmony_ci		((u32)global_gain) * gain / GAIN_MAX;
31462306a36Sopenharmony_ci	if (normalized > GAIN_MAX) {
31562306a36Sopenharmony_ci		gspca_dbg(gspca_dev, D_STREAM, "Truncating gain 0x%04X w/ value 0x%04X\n\n",
31662306a36Sopenharmony_ci			  GAIN_MAX, normalized);
31762306a36Sopenharmony_ci		normalized = GAIN_MAX;
31862306a36Sopenharmony_ci	}
31962306a36Sopenharmony_ci	normalized = gainify(normalized);
32062306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "gain R (0x%04X): 0x%04X w / source 0x%04X\n\n",
32162306a36Sopenharmony_ci		  REG_RED_GAIN, normalized, gain);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	reg_w(gspca_dev, normalized, REG_RED_GAIN);
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic void configure_wh(struct gspca_dev *gspca_dev)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	unsigned int w = gspca_dev->pixfmt.width;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "configure_wh\n\n");
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	if (w == 800) {
33362306a36Sopenharmony_ci		static const struct cmd reg_init_res[] = {
33462306a36Sopenharmony_ci			{0x0060, REG_X_ADDR_START},
33562306a36Sopenharmony_ci			{0x0CD9, REG_X_ADDR_END},
33662306a36Sopenharmony_ci			{0x0036, REG_Y_ADDR_START},
33762306a36Sopenharmony_ci			{0x098F, REG_Y_ADDR_END},
33862306a36Sopenharmony_ci			{0x07C7, REG_READ_MODE},
33962306a36Sopenharmony_ci		};
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci		reg_w_buf(gspca_dev,
34262306a36Sopenharmony_ci			       reg_init_res, ARRAY_SIZE(reg_init_res));
34362306a36Sopenharmony_ci	} else if (w == 1600) {
34462306a36Sopenharmony_ci		static const struct cmd reg_init_res[] = {
34562306a36Sopenharmony_ci			{0x009C, REG_X_ADDR_START},
34662306a36Sopenharmony_ci			{0x0D19, REG_X_ADDR_END},
34762306a36Sopenharmony_ci			{0x0068, REG_Y_ADDR_START},
34862306a36Sopenharmony_ci			{0x09C5, REG_Y_ADDR_END},
34962306a36Sopenharmony_ci			{0x06C3, REG_READ_MODE},
35062306a36Sopenharmony_ci		};
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci		reg_w_buf(gspca_dev,
35362306a36Sopenharmony_ci			       reg_init_res, ARRAY_SIZE(reg_init_res));
35462306a36Sopenharmony_ci	} else if (w == 3264) {
35562306a36Sopenharmony_ci		static const struct cmd reg_init_res[] = {
35662306a36Sopenharmony_ci			{0x00E8, REG_X_ADDR_START},
35762306a36Sopenharmony_ci			{0x0DA7, REG_X_ADDR_END},
35862306a36Sopenharmony_ci			{0x009E, REG_Y_ADDR_START},
35962306a36Sopenharmony_ci			{0x0A2D, REG_Y_ADDR_END},
36062306a36Sopenharmony_ci			{0x0241, REG_READ_MODE},
36162306a36Sopenharmony_ci		};
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci		reg_w_buf(gspca_dev,
36462306a36Sopenharmony_ci			       reg_init_res, ARRAY_SIZE(reg_init_res));
36562306a36Sopenharmony_ci	} else {
36662306a36Sopenharmony_ci		gspca_err(gspca_dev, "bad width %u\n", w);
36762306a36Sopenharmony_ci		gspca_dev->usb_err = -EINVAL;
36862306a36Sopenharmony_ci		return;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	reg_w(gspca_dev, 0x0000, REG_SCALING_MODE);
37262306a36Sopenharmony_ci	reg_w(gspca_dev, 0x0010, REG_SCALE_M);
37362306a36Sopenharmony_ci	reg_w(gspca_dev, w, REG_X_OUTPUT_SIZE);
37462306a36Sopenharmony_ci	reg_w(gspca_dev, gspca_dev->pixfmt.height, REG_Y_OUTPUT_SIZE);
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	if (w == 800) {
37762306a36Sopenharmony_ci		reg_w(gspca_dev, 0x0384, REG_FRAME_LENGTH_LINES_);
37862306a36Sopenharmony_ci		reg_w(gspca_dev, 0x0960, REG_LINE_LENGTH_PCK_);
37962306a36Sopenharmony_ci	} else if (w == 1600) {
38062306a36Sopenharmony_ci		reg_w(gspca_dev, 0x0640, REG_FRAME_LENGTH_LINES_);
38162306a36Sopenharmony_ci		reg_w(gspca_dev, 0x0FA0, REG_LINE_LENGTH_PCK_);
38262306a36Sopenharmony_ci	} else if (w == 3264) {
38362306a36Sopenharmony_ci		reg_w(gspca_dev, 0x0B4B, REG_FRAME_LENGTH_LINES_);
38462306a36Sopenharmony_ci		reg_w(gspca_dev, 0x1F40, REG_LINE_LENGTH_PCK_);
38562306a36Sopenharmony_ci	} else {
38662306a36Sopenharmony_ci		gspca_err(gspca_dev, "bad width %u\n", w);
38762306a36Sopenharmony_ci		gspca_dev->usb_err = -EINVAL;
38862306a36Sopenharmony_ci		return;
38962306a36Sopenharmony_ci	}
39062306a36Sopenharmony_ci}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci/* Packets that were encrypted, no idea if the grouping is significant */
39362306a36Sopenharmony_cistatic void configure_encrypted(struct gspca_dev *gspca_dev)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	static const struct cmd reg_init_begin[] = {
39662306a36Sopenharmony_ci		{0x0100, REG_SOFTWARE_RESET},
39762306a36Sopenharmony_ci		{0x0000, REG_MODE_SELECT},
39862306a36Sopenharmony_ci		{0x0100, REG_GROUPED_PARAMETER_HOLD},
39962306a36Sopenharmony_ci		{0x0004, REG_VT_PIX_CLK_DIV},
40062306a36Sopenharmony_ci		{0x0001, REG_VT_SYS_CLK_DIV},
40162306a36Sopenharmony_ci		{0x0008, REG_OP_PIX_CLK_DIV},
40262306a36Sopenharmony_ci		{0x0001, REG_OP_SYS_CLK_DIV},
40362306a36Sopenharmony_ci		{0x0004, REG_PRE_PLL_CLK_DIV},
40462306a36Sopenharmony_ci		{0x0040, REG_PLL_MULTIPLIER},
40562306a36Sopenharmony_ci		{0x0000, REG_GROUPED_PARAMETER_HOLD},
40662306a36Sopenharmony_ci		{0x0100, REG_GROUPED_PARAMETER_HOLD},
40762306a36Sopenharmony_ci	};
40862306a36Sopenharmony_ci	static const struct cmd reg_init_end[] = {
40962306a36Sopenharmony_ci		{0x0000, REG_GROUPED_PARAMETER_HOLD},
41062306a36Sopenharmony_ci		{0x0301, 0x31AE},
41162306a36Sopenharmony_ci		{0x0805, 0x3064},
41262306a36Sopenharmony_ci		{0x0071, 0x3170},
41362306a36Sopenharmony_ci		{0x10DE, REG_RESET_REGISTER},
41462306a36Sopenharmony_ci		{0x0000, REG_MODE_SELECT},
41562306a36Sopenharmony_ci		{0x0010, REG_PLL_MULTIPLIER},
41662306a36Sopenharmony_ci		{0x0100, REG_MODE_SELECT},
41762306a36Sopenharmony_ci	};
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "Encrypted begin, w = %u\n\n",
42062306a36Sopenharmony_ci		  gspca_dev->pixfmt.width);
42162306a36Sopenharmony_ci	reg_w_buf(gspca_dev, reg_init_begin, ARRAY_SIZE(reg_init_begin));
42262306a36Sopenharmony_ci	configure_wh(gspca_dev);
42362306a36Sopenharmony_ci	reg_w_buf(gspca_dev, reg_init_end, ARRAY_SIZE(reg_init_end));
42462306a36Sopenharmony_ci	reg_w(gspca_dev, 0x0100, REG_GROUPED_PARAMETER_HOLD);
42562306a36Sopenharmony_ci	reg_w(gspca_dev, 0x0000, REG_GROUPED_PARAMETER_HOLD);
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "Encrypted end\n\n");
42862306a36Sopenharmony_ci}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_cistatic int configure(struct gspca_dev *gspca_dev)
43162306a36Sopenharmony_ci{
43262306a36Sopenharmony_ci	int rc;
43362306a36Sopenharmony_ci	char *buff = gspca_dev->usb_buf;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "configure()\n\n");
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	/*
43862306a36Sopenharmony_ci	 * First driver sets a sort of encryption key
43962306a36Sopenharmony_ci	 * A number of futur requests of this type have wValue and wIndex
44062306a36Sopenharmony_ci	 * encrypted as follows:
44162306a36Sopenharmony_ci	 * -Compute key = this wValue rotate left by 4 bits
44262306a36Sopenharmony_ci	 *	(decrypt.py rotates right because we are decrypting)
44362306a36Sopenharmony_ci	 * -Later packets encrypt packets by XOR'ing with key
44462306a36Sopenharmony_ci	 *	XOR encrypt/decrypt is symmetrical
44562306a36Sopenharmony_ci	 *	wValue, and wIndex are encrypted
44662306a36Sopenharmony_ci	 *	bRequest is not and bRequestType is always 0xC0
44762306a36Sopenharmony_ci	 *		This allows resyncing if key is unknown?
44862306a36Sopenharmony_ci	 * By setting 0 we XOR with 0 and the shifting and XOR drops out
44962306a36Sopenharmony_ci	 */
45062306a36Sopenharmony_ci	rc = usb_control_msg(gspca_dev->dev, usb_rcvctrlpipe(gspca_dev->dev, 0),
45162306a36Sopenharmony_ci			     0x16, 0xC0, 0x0000, 0x0000, buff, 2, 500);
45262306a36Sopenharmony_ci	if (val_reply(gspca_dev, buff, rc)) {
45362306a36Sopenharmony_ci		gspca_err(gspca_dev, "failed key req\n");
45462306a36Sopenharmony_ci		return -EIO;
45562306a36Sopenharmony_ci	}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	/*
45862306a36Sopenharmony_ci	 * Next does some sort of 2 packet challenge / response
45962306a36Sopenharmony_ci	 * evidence suggests its an Atmel I2C crypto part but nobody cares to
46062306a36Sopenharmony_ci	 * look
46162306a36Sopenharmony_ci	 * (to make sure its not cloned hardware?)
46262306a36Sopenharmony_ci	 * Ignore: I want to work with their hardware, not clone it
46362306a36Sopenharmony_ci	 * 16 bytes out challenge, requestType: 0x40
46462306a36Sopenharmony_ci	 * 16 bytes in response, requestType: 0xC0
46562306a36Sopenharmony_ci	 */
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	rc = usb_control_msg(gspca_dev->dev, usb_sndctrlpipe(gspca_dev->dev, 0),
46862306a36Sopenharmony_ci			     0x01, 0x40, 0x0001, 0x000F, NULL, 0, 500);
46962306a36Sopenharmony_ci	if (rc < 0) {
47062306a36Sopenharmony_ci		gspca_err(gspca_dev, "failed to replay packet 176 w/ rc %d\n",
47162306a36Sopenharmony_ci			  rc);
47262306a36Sopenharmony_ci		return rc;
47362306a36Sopenharmony_ci	}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	rc = usb_control_msg(gspca_dev->dev, usb_sndctrlpipe(gspca_dev->dev, 0),
47662306a36Sopenharmony_ci			     0x01, 0x40, 0x0000, 0x000F, NULL, 0, 500);
47762306a36Sopenharmony_ci	if (rc < 0) {
47862306a36Sopenharmony_ci		gspca_err(gspca_dev, "failed to replay packet 178 w/ rc %d\n",
47962306a36Sopenharmony_ci			  rc);
48062306a36Sopenharmony_ci		return rc;
48162306a36Sopenharmony_ci	}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	rc = usb_control_msg(gspca_dev->dev, usb_sndctrlpipe(gspca_dev->dev, 0),
48462306a36Sopenharmony_ci			     0x01, 0x40, 0x0001, 0x000F, NULL, 0, 500);
48562306a36Sopenharmony_ci	if (rc < 0) {
48662306a36Sopenharmony_ci		gspca_err(gspca_dev, "failed to replay packet 180 w/ rc %d\n",
48762306a36Sopenharmony_ci			  rc);
48862306a36Sopenharmony_ci		return rc;
48962306a36Sopenharmony_ci	}
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	/*
49262306a36Sopenharmony_ci	 * Serial number?  Doesn't seem to be required
49362306a36Sopenharmony_ci	 * cam1: \xE6\x0D\x00\x00, cam2: \x70\x19\x00\x00
49462306a36Sopenharmony_ci	 * rc = usb_control_msg(gspca_dev->dev,
49562306a36Sopenharmony_ci	 *			usb_rcvctrlpipe(gspca_dev->dev, 0),
49662306a36Sopenharmony_ci	 *			0x20, 0xC0, 0x0000, 0x0000, buff, 4, 500);
49762306a36Sopenharmony_ci	 */
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	/* Large (EEPROM?) read, skip it since no idea what to do with it */
50062306a36Sopenharmony_ci	gspca_dev->usb_err = 0;
50162306a36Sopenharmony_ci	configure_encrypted(gspca_dev);
50262306a36Sopenharmony_ci	if (gspca_dev->usb_err)
50362306a36Sopenharmony_ci		return gspca_dev->usb_err;
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	/* Omitted this by accident, does not work without it */
50662306a36Sopenharmony_ci	rc = usb_control_msg(gspca_dev->dev, usb_sndctrlpipe(gspca_dev->dev, 0),
50762306a36Sopenharmony_ci			     0x01, 0x40, 0x0003, 0x000F, NULL, 0, 500);
50862306a36Sopenharmony_ci	if (rc < 0) {
50962306a36Sopenharmony_ci		gspca_err(gspca_dev, "failed to replay final packet w/ rc %d\n",
51062306a36Sopenharmony_ci			  rc);
51162306a36Sopenharmony_ci		return rc;
51262306a36Sopenharmony_ci	}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	gspca_dbg(gspca_dev, D_STREAM, "Configure complete\n\n");
51562306a36Sopenharmony_ci	return 0;
51662306a36Sopenharmony_ci}
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_cistatic int sd_config(struct gspca_dev *gspca_dev,
51962306a36Sopenharmony_ci		     const struct usb_device_id *id)
52062306a36Sopenharmony_ci{
52162306a36Sopenharmony_ci	gspca_dev->cam.cam_mode = vga_mode;
52262306a36Sopenharmony_ci	gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode);
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	/* Yes we want URBs and we want them now! */
52562306a36Sopenharmony_ci	gspca_dev->cam.no_urb_create = 0;
52662306a36Sopenharmony_ci	gspca_dev->cam.bulk_nurbs = 4;
52762306a36Sopenharmony_ci	/* Largest size the windows driver uses */
52862306a36Sopenharmony_ci	gspca_dev->cam.bulk_size = BULK_SIZE;
52962306a36Sopenharmony_ci	/* Def need to use bulk transfers */
53062306a36Sopenharmony_ci	gspca_dev->cam.bulk = 1;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	return 0;
53362306a36Sopenharmony_ci}
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_cistatic int sd_start(struct gspca_dev *gspca_dev)
53662306a36Sopenharmony_ci{
53762306a36Sopenharmony_ci	struct sd *sd = (struct sd *) gspca_dev;
53862306a36Sopenharmony_ci	int rc;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	sd->this_f = 0;
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	rc = configure(gspca_dev);
54362306a36Sopenharmony_ci	if (rc < 0) {
54462306a36Sopenharmony_ci		gspca_err(gspca_dev, "Failed configure\n");
54562306a36Sopenharmony_ci		return rc;
54662306a36Sopenharmony_ci	}
54762306a36Sopenharmony_ci	/* First two frames have messed up gains
54862306a36Sopenharmony_ci	Drop them to avoid special cases in user apps? */
54962306a36Sopenharmony_ci	return 0;
55062306a36Sopenharmony_ci}
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_cistatic void sd_pkt_scan(struct gspca_dev *gspca_dev,
55362306a36Sopenharmony_ci			u8 *data,	/* isoc packet */
55462306a36Sopenharmony_ci			int len)	/* iso packet length */
55562306a36Sopenharmony_ci{
55662306a36Sopenharmony_ci	struct sd *sd = (struct sd *) gspca_dev;
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	if (len != BULK_SIZE) {
55962306a36Sopenharmony_ci		/* can we finish a frame? */
56062306a36Sopenharmony_ci		if (sd->this_f + len == gspca_dev->pixfmt.sizeimage) {
56162306a36Sopenharmony_ci			gspca_frame_add(gspca_dev, LAST_PACKET, data, len);
56262306a36Sopenharmony_ci			gspca_dbg(gspca_dev, D_FRAM, "finish frame sz %u/%u w/ len %u\n\n",
56362306a36Sopenharmony_ci				  sd->this_f, gspca_dev->pixfmt.sizeimage, len);
56462306a36Sopenharmony_ci		/* lost some data, discard the frame */
56562306a36Sopenharmony_ci		} else {
56662306a36Sopenharmony_ci			gspca_frame_add(gspca_dev, DISCARD_PACKET, NULL, 0);
56762306a36Sopenharmony_ci			gspca_dbg(gspca_dev, D_FRAM, "abort frame sz %u/%u w/ len %u\n\n",
56862306a36Sopenharmony_ci				  sd->this_f, gspca_dev->pixfmt.sizeimage, len);
56962306a36Sopenharmony_ci		}
57062306a36Sopenharmony_ci		sd->this_f = 0;
57162306a36Sopenharmony_ci	} else {
57262306a36Sopenharmony_ci		if (sd->this_f == 0)
57362306a36Sopenharmony_ci			gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
57462306a36Sopenharmony_ci		else
57562306a36Sopenharmony_ci			gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
57662306a36Sopenharmony_ci		sd->this_f += len;
57762306a36Sopenharmony_ci	}
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cistatic int sd_init(struct gspca_dev *gspca_dev)
58162306a36Sopenharmony_ci{
58262306a36Sopenharmony_ci	return 0;
58362306a36Sopenharmony_ci}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_cistatic int sd_s_ctrl(struct v4l2_ctrl *ctrl)
58662306a36Sopenharmony_ci{
58762306a36Sopenharmony_ci	struct gspca_dev *gspca_dev =
58862306a36Sopenharmony_ci		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
58962306a36Sopenharmony_ci	struct sd *sd = (struct sd *) gspca_dev;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	gspca_dev->usb_err = 0;
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	if (!gspca_dev->streaming)
59462306a36Sopenharmony_ci		return 0;
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	switch (ctrl->id) {
59762306a36Sopenharmony_ci	case V4L2_CID_EXPOSURE:
59862306a36Sopenharmony_ci		setexposure(gspca_dev, ctrl->val);
59962306a36Sopenharmony_ci		break;
60062306a36Sopenharmony_ci	case V4L2_CID_GAIN:
60162306a36Sopenharmony_ci		/* gspca_dev->gain automatically updated */
60262306a36Sopenharmony_ci		setggain(gspca_dev, gspca_dev->gain->val);
60362306a36Sopenharmony_ci		break;
60462306a36Sopenharmony_ci	case V4L2_CID_BLUE_BALANCE:
60562306a36Sopenharmony_ci		sd->blue->val = ctrl->val;
60662306a36Sopenharmony_ci		setbgain(gspca_dev, sd->blue->val, gspca_dev->gain->val);
60762306a36Sopenharmony_ci		break;
60862306a36Sopenharmony_ci	case V4L2_CID_RED_BALANCE:
60962306a36Sopenharmony_ci		sd->red->val = ctrl->val;
61062306a36Sopenharmony_ci		setrgain(gspca_dev, sd->red->val, gspca_dev->gain->val);
61162306a36Sopenharmony_ci		break;
61262306a36Sopenharmony_ci	}
61362306a36Sopenharmony_ci	return gspca_dev->usb_err;
61462306a36Sopenharmony_ci}
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops sd_ctrl_ops = {
61762306a36Sopenharmony_ci	.s_ctrl = sd_s_ctrl,
61862306a36Sopenharmony_ci};
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_cistatic int sd_init_controls(struct gspca_dev *gspca_dev)
62162306a36Sopenharmony_ci{
62262306a36Sopenharmony_ci	struct sd *sd = (struct sd *) gspca_dev;
62362306a36Sopenharmony_ci	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	gspca_dev->vdev.ctrl_handler = hdl;
62662306a36Sopenharmony_ci	v4l2_ctrl_handler_init(hdl, 4);
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
62962306a36Sopenharmony_ci	/* Mostly limited by URB timeouts */
63062306a36Sopenharmony_ci	/* XXX: make dynamic based on frame rate? */
63162306a36Sopenharmony_ci		V4L2_CID_EXPOSURE, 0, 800, 1, 350);
63262306a36Sopenharmony_ci	gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
63362306a36Sopenharmony_ci			V4L2_CID_GAIN, 0, 511, 1, 128);
63462306a36Sopenharmony_ci	sd->blue = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
63562306a36Sopenharmony_ci			V4L2_CID_BLUE_BALANCE, 0, 1023, 1, 80);
63662306a36Sopenharmony_ci	sd->red = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
63762306a36Sopenharmony_ci			V4L2_CID_RED_BALANCE, 0, 1023, 1, 295);
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	if (hdl->error) {
64062306a36Sopenharmony_ci		gspca_err(gspca_dev, "Could not initialize controls\n");
64162306a36Sopenharmony_ci		return hdl->error;
64262306a36Sopenharmony_ci	}
64362306a36Sopenharmony_ci	return 0;
64462306a36Sopenharmony_ci}
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci/* sub-driver description */
64762306a36Sopenharmony_cistatic const struct sd_desc sd_desc = {
64862306a36Sopenharmony_ci	.name = MODULE_NAME,
64962306a36Sopenharmony_ci	.config = sd_config,
65062306a36Sopenharmony_ci	.init = sd_init,
65162306a36Sopenharmony_ci	.init_controls = sd_init_controls,
65262306a36Sopenharmony_ci	.start = sd_start,
65362306a36Sopenharmony_ci	.pkt_scan = sd_pkt_scan,
65462306a36Sopenharmony_ci};
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci/* Table of supported USB devices */
65762306a36Sopenharmony_cistatic const struct usb_device_id device_table[] = {
65862306a36Sopenharmony_ci	/* Commented out devices should be related */
65962306a36Sopenharmony_ci	/* AS: AmScope, TT: ToupTek */
66062306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6035) },  TT UCMOS00350KPA */
66162306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6130) },  TT UCMOS01300KPA */
66262306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6200) },  TT UCMOS02000KPA */
66362306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6310) },  TT UCMOS03100KPA */
66462306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6510) },  TT UCMOS05100KPA */
66562306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6800) },  TT UCMOS08000KPA */
66662306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6801) },  TT UCMOS08000KPB */
66762306a36Sopenharmony_ci	{ USB_DEVICE(0x0547, 0x6801) }, /* TT UCMOS08000KPB, AS MU800 */
66862306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6900) },  TT UCMOS09000KPA */
66962306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6901) },  TT UCMOS09000KPB */
67062306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6010) },  TT UCMOS10000KPA */
67162306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6014) },  TT UCMOS14000KPA */
67262306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6131) },  TT UCMOS01300KMA */
67362306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x6511) },  TT UCMOS05100KMA */
67462306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8080) },  TT UHCCD00800KPA */
67562306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8140) },  TT UHCCD01400KPA */
67662306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8141) },  TT EXCCD01400KPA */
67762306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8200) },  TT UHCCD02000KPA */
67862306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8201) },  TT UHCCD02000KPB */
67962306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8310) },  TT UHCCD03100KPA */
68062306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8500) },  TT UHCCD05000KPA */
68162306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8510) },  TT UHCCD05100KPA */
68262306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8600) },  TT UHCCD06000KPA */
68362306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8800) },  TT UHCCD08000KPA */
68462306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x8315) },  TT UHCCD03150KPA */
68562306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x7800) },  TT UHCCD00800KMA */
68662306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x7140) },  TT UHCCD01400KMA */
68762306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x7141) },  TT UHCCD01400KMB */
68862306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x7200) },  TT UHCCD02000KMA */
68962306a36Sopenharmony_ci	/* { USB_DEVICE(0x0547, 0x7315) },  TT UHCCD03150KMA */
69062306a36Sopenharmony_ci	{ }
69162306a36Sopenharmony_ci};
69262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, device_table);
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_cistatic int sd_probe(struct usb_interface *intf,
69562306a36Sopenharmony_ci		    const struct usb_device_id *id)
69662306a36Sopenharmony_ci{
69762306a36Sopenharmony_ci	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
69862306a36Sopenharmony_ci			     THIS_MODULE);
69962306a36Sopenharmony_ci}
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_cistatic struct usb_driver sd_driver = {
70262306a36Sopenharmony_ci	.name = MODULE_NAME,
70362306a36Sopenharmony_ci	.id_table = device_table,
70462306a36Sopenharmony_ci	.probe = sd_probe,
70562306a36Sopenharmony_ci	.disconnect = gspca_disconnect,
70662306a36Sopenharmony_ci#ifdef CONFIG_PM
70762306a36Sopenharmony_ci	.suspend = gspca_suspend,
70862306a36Sopenharmony_ci	.resume = gspca_resume,
70962306a36Sopenharmony_ci#endif
71062306a36Sopenharmony_ci};
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_cistatic int __init sd_mod_init(void)
71362306a36Sopenharmony_ci{
71462306a36Sopenharmony_ci	int ret;
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_ci	ret = usb_register(&sd_driver);
71762306a36Sopenharmony_ci	if (ret < 0)
71862306a36Sopenharmony_ci		return ret;
71962306a36Sopenharmony_ci	return 0;
72062306a36Sopenharmony_ci}
72162306a36Sopenharmony_cistatic void __exit sd_mod_exit(void)
72262306a36Sopenharmony_ci{
72362306a36Sopenharmony_ci	usb_deregister(&sd_driver);
72462306a36Sopenharmony_ci}
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_cimodule_init(sd_mod_init);
72762306a36Sopenharmony_cimodule_exit(sd_mod_exit);
728