[media] gspca - ov519: New sensor ov7660 with bridge ov530 (ov519)

[mchehab@redhat.com: Some CodingStyle fixes]
Tested-by: Anca Emanuel <anca.emanuel@gmail.com>
Signed-off-by: Jean-François Moine <moinejf@free.fr>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/video/gspca/ov519.c b/drivers/media/video/gspca/ov519.c
index 08f07c3..0ed21db 100644
--- a/drivers/media/video/gspca/ov519.c
+++ b/drivers/media/video/gspca/ov519.c
@@ -82,7 +82,7 @@
 #define BRIDGE_OV511PLUS	1
 #define BRIDGE_OV518		2
 #define BRIDGE_OV518PLUS	3
-#define BRIDGE_OV519		4
+#define BRIDGE_OV519		4		/* = ov530 */
 #define BRIDGE_OVFX2		5
 #define BRIDGE_W9968CF		6
 #define BRIDGE_MASK		7
@@ -127,6 +127,7 @@
 	SEN_OV7620AE,
 	SEN_OV7640,
 	SEN_OV7648,
+	SEN_OV7660,
 	SEN_OV7670,
 	SEN_OV76BE,
 	SEN_OV8610,
@@ -183,7 +184,7 @@
 	    },
 	    .set_control = setcolors,
 	},
-/* The flip controls work with ov7670 only */
+/* The flip controls work for sensors ov7660 and ov7670 only */
 [HFLIP] = {
 	    {
 		.id      = V4L2_CID_HFLIP,
@@ -268,6 +269,8 @@
 			(1 << AUTOBRIGHT) |
 			(1 << CONTRAST),
 
+[SEN_OV7660] =		(1 << AUTOBRIGHT),
+
 [SEN_OV7670] =		(1 << COLORS) |
 			(1 << AUTOBRIGHT),
 
@@ -572,7 +575,7 @@
 #define OV7610_REG_ID_LOW	0x1d	/* manufacturer ID LSB */
 #define OV7610_REG_COM_I	0x29	/* misc settings */
 
-/* OV7670 registers */
+/* OV7660 and OV7670 registers */
 #define OV7670_R00_GAIN		0x00	/* Gain lower 8 bits (rest in vref) */
 #define OV7670_R01_BLUE		0x01	/* blue gain */
 #define OV7670_R02_RED		0x02	/* red gain */
@@ -625,6 +628,7 @@
 /*#define   OV7670_COM15_R00FF	 0xc0	 *	00 to FF */
 #define OV7670_R41_COM16	0x41	/* Control 16 */
 #define   OV7670_COM16_AWBGAIN	 0x08	/* AWB gain enable */
+/* end of ov7660 common registers */
 #define OV7670_R55_BRIGHT	0x55	/* Brightness */
 #define OV7670_R56_CONTRAS	0x56	/* Contrast control */
 #define OV7670_R69_GFIX		0x69	/* Fix gain control */
@@ -1577,6 +1581,150 @@
 	{ 0x12, 0x14 },
 };
 
+static const struct ov_regvals init_519_ov7660[] = {
+	{ 0x5d,	0x03 }, /* Turn off suspend mode */
+	{ 0x53,	0x9b }, /* 0x9f enables the (unused) microcontroller */
+	{ 0x54,	0x0f }, /* bit2 (jpeg enable) */
+	{ 0xa2,	0x20 }, /* a2-a5 are undocumented */
+	{ 0xa3,	0x18 },
+	{ 0xa4,	0x04 },
+	{ 0xa5,	0x28 },
+	{ 0x37,	0x00 },	/* SetUsbInit */
+	{ 0x55,	0x02 }, /* 4.096 Mhz audio clock */
+	/* Enable both fields, YUV Input, disable defect comp (why?) */
+	{ 0x20,	0x0c },	/* 0x0d does U <-> V swap */
+	{ 0x21,	0x38 },
+	{ 0x22,	0x1d },
+	{ 0x17,	0x50 }, /* undocumented */
+	{ 0x37,	0x00 }, /* undocumented */
+	{ 0x40,	0xff }, /* I2C timeout counter */
+	{ 0x46,	0x00 }, /* I2C clock prescaler */
+};
+static const struct ov_i2c_regvals norm_7660[] = {
+	{OV7670_R12_COM7, OV7670_COM7_RESET},
+	{OV7670_R11_CLKRC, 0x81},
+	{0x92, 0x00},			/* DM_LNL */
+	{0x93, 0x00},			/* DM_LNH */
+	{0x9d, 0x4c},			/* BD50ST */
+	{0x9e, 0x3f},			/* BD60ST */
+	{OV7670_R3B_COM11, 0x02},
+	{OV7670_R13_COM8, 0xf5},
+	{OV7670_R10_AECH, 0x00},
+	{OV7670_R00_GAIN, 0x00},
+	{OV7670_R01_BLUE, 0x7c},
+	{OV7670_R02_RED, 0x9d},
+	{OV7670_R12_COM7, 0x00},
+	{OV7670_R04_COM1, 00},
+	{OV7670_R18_HSTOP, 0x01},
+	{OV7670_R17_HSTART, 0x13},
+	{OV7670_R32_HREF, 0x92},
+	{OV7670_R19_VSTART, 0x02},
+	{OV7670_R1A_VSTOP, 0x7a},
+	{OV7670_R03_VREF, 0x00},
+	{OV7670_R0E_COM5, 0x04},
+	{OV7670_R0F_COM6, 0x62},
+	{OV7670_R15_COM10, 0x00},
+	{0x16, 0x02},			/* RSVD */
+	{0x1b, 0x00},			/* PSHFT */
+	{OV7670_R1E_MVFP, 0x01},
+	{0x29, 0x3c},			/* RSVD */
+	{0x33, 0x00},			/* CHLF */
+	{0x34, 0x07},			/* ARBLM */
+	{0x35, 0x84},			/* RSVD */
+	{0x36, 0x00},			/* RSVD */
+	{0x37, 0x04},			/* ADC */
+	{0x39, 0x43},			/* OFON */
+	{OV7670_R3A_TSLB, 0x00},
+	{OV7670_R3C_COM12, 0x6c},
+	{OV7670_R3D_COM13, 0x98},
+	{OV7670_R3F_EDGE, 0x23},
+	{OV7670_R40_COM15, 0xc1},
+	{OV7670_R41_COM16, 0x22},
+	{0x6b, 0x0a},			/* DBLV */
+	{0xa1, 0x08},			/* RSVD */
+	{0x69, 0x80},			/* HV */
+	{0x43, 0xf0},			/* RSVD.. */
+	{0x44, 0x10},
+	{0x45, 0x78},
+	{0x46, 0xa8},
+	{0x47, 0x60},
+	{0x48, 0x80},
+	{0x59, 0xba},
+	{0x5a, 0x9a},
+	{0x5b, 0x22},
+	{0x5c, 0xb9},
+	{0x5d, 0x9b},
+	{0x5e, 0x10},
+	{0x5f, 0xe0},
+	{0x60, 0x85},
+	{0x61, 0x60},
+	{0x9f, 0x9d},			/* RSVD */
+	{0xa0, 0xa0},			/* DSPC2 */
+	{0x4f, 0x60},			/* matrix */
+	{0x50, 0x64},
+	{0x51, 0x04},
+	{0x52, 0x18},
+	{0x53, 0x3c},
+	{0x54, 0x54},
+	{0x55, 0x40},
+	{0x56, 0x40},
+	{0x57, 0x40},
+	{0x58, 0x0d},			/* matrix sign */
+	{0x8b, 0xcc},			/* RSVD */
+	{0x8c, 0xcc},
+	{0x8d, 0xcf},
+	{0x6c, 0x40},			/* gamma curve */
+	{0x6d, 0xe0},
+	{0x6e, 0xa0},
+	{0x6f, 0x80},
+	{0x70, 0x70},
+	{0x71, 0x80},
+	{0x72, 0x60},
+	{0x73, 0x60},
+	{0x74, 0x50},
+	{0x75, 0x40},
+	{0x76, 0x38},
+	{0x77, 0x3c},
+	{0x78, 0x32},
+	{0x79, 0x1a},
+	{0x7a, 0x28},
+	{0x7b, 0x24},
+	{0x7c, 0x04},			/* gamma curve */
+	{0x7d, 0x12},
+	{0x7e, 0x26},
+	{0x7f, 0x46},
+	{0x80, 0x54},
+	{0x81, 0x64},
+	{0x82, 0x70},
+	{0x83, 0x7c},
+	{0x84, 0x86},
+	{0x85, 0x8e},
+	{0x86, 0x9c},
+	{0x87, 0xab},
+	{0x88, 0xc4},
+	{0x89, 0xd1},
+	{0x8a, 0xe5},
+	{OV7670_R14_COM9, 0x1e},
+	{OV7670_R24_AEW, 0x80},
+	{OV7670_R25_AEB, 0x72},
+	{OV7670_R26_VPT, 0xb3},
+	{0x62, 0x80},			/* LCC1 */
+	{0x63, 0x80},			/* LCC2 */
+	{0x64, 0x06},			/* LCC3 */
+	{0x65, 0x00},			/* LCC4 */
+	{0x66, 0x01},			/* LCC5 */
+	{0x94, 0x0e},			/* RSVD.. */
+	{0x95, 0x14},
+	{OV7670_R13_COM8, OV7670_COM8_FASTAEC
+			| OV7670_COM8_AECSTEP
+			| OV7670_COM8_BFILT
+			| 0x10
+			| OV7670_COM8_AGC
+			| OV7670_COM8_AWB
+			| OV7670_COM8_AEC},
+	{0xa1, 0xc8}
+};
+
 /* 7670. Defaults taken from OmniVision provided data,
 *  as provided by Jonathan Corbet of OLPC		*/
 static const struct ov_i2c_regvals norm_7670[] = {
@@ -2574,6 +2722,11 @@
 				PDEBUG(D_PROBE, "Sensor is an OV7648");
 				sd->sensor = SEN_OV7648;
 				break;
+			case 0x60:
+				PDEBUG(D_PROBE, "Sensor is a OV7660");
+				sd->sensor = SEN_OV7660;
+				sd->invert_led = 0;
+				break;
 			default:
 				PDEBUG(D_PROBE, "Unknown sensor: 0x76%x", low);
 				return;
@@ -2935,6 +3088,91 @@
 	write_regvals(sd, init_fx2, ARRAY_SIZE(init_fx2));
 }
 
+/* set the mode */
+/* This function works for ov7660 only */
+static void ov519_set_mode(struct sd *sd)
+{
+	static const struct ov_regvals bridge_ov7660[2][10] = {
+		{{0x10, 0x14}, {0x11, 0x1e}, {0x12, 0x00}, {0x13, 0x00},
+		 {0x14, 0x00}, {0x15, 0x00}, {0x16, 0x00}, {0x20, 0x0c},
+		 {0x25, 0x01}, {0x26, 0x00}},
+		{{0x10, 0x28}, {0x11, 0x3c}, {0x12, 0x00}, {0x13, 0x00},
+		 {0x14, 0x00}, {0x15, 0x00}, {0x16, 0x00}, {0x20, 0x0c},
+		 {0x25, 0x03}, {0x26, 0x00}}
+	};
+	static const struct ov_i2c_regvals sensor_ov7660[2][3] = {
+		{{0x12, 0x00}, {0x24, 0x00}, {0x0c, 0x0c}},
+		{{0x12, 0x00}, {0x04, 0x00}, {0x0c, 0x00}}
+	};
+	static const struct ov_i2c_regvals sensor_ov7660_2[] = {
+		{OV7670_R17_HSTART, 0x13},
+		{OV7670_R18_HSTOP, 0x01},
+		{OV7670_R32_HREF, 0x92},
+		{OV7670_R19_VSTART, 0x02},
+		{OV7670_R1A_VSTOP, 0x7a},
+		{OV7670_R03_VREF, 0x00},
+/*		{0x33, 0x00}, */
+/*		{0x34, 0x07}, */
+/*		{0x36, 0x00}, */
+/*		{0x6b, 0x0a}, */
+	};
+
+	write_regvals(sd, bridge_ov7660[sd->gspca_dev.curr_mode],
+			ARRAY_SIZE(bridge_ov7660[0]));
+	write_i2c_regvals(sd, sensor_ov7660[sd->gspca_dev.curr_mode],
+			ARRAY_SIZE(sensor_ov7660[0]));
+	write_i2c_regvals(sd, sensor_ov7660_2,
+			ARRAY_SIZE(sensor_ov7660_2));
+}
+
+/* set the frame rate */
+/* This function works for sensors ov7640, ov7648 ov7660 and ov7670 only */
+static void ov519_set_fr(struct sd *sd)
+{
+	int fr;
+	u8 clock;
+	/* frame rate table with indices:
+	 *	- mode = 0: 320x240, 1: 640x480
+	 *	- fr rate = 0: 30, 1: 25, 2: 20, 3: 15, 4: 10, 5: 5
+	 *	- reg = 0: bridge a4, 1: bridge 23, 2: sensor 11 (clock)
+	 */
+	static const u8 fr_tb[2][6][3] = {
+		{{0x04, 0xff, 0x00},
+		 {0x04, 0x1f, 0x00},
+		 {0x04, 0x1b, 0x00},
+		 {0x04, 0x15, 0x00},
+		 {0x04, 0x09, 0x00},
+		 {0x04, 0x01, 0x00}},
+		{{0x0c, 0xff, 0x00},
+		 {0x0c, 0x1f, 0x00},
+		 {0x0c, 0x1b, 0x00},
+		 {0x04, 0xff, 0x01},
+		 {0x04, 0x1f, 0x01},
+		 {0x04, 0x1b, 0x01}},
+	};
+
+	if (frame_rate > 0)
+		sd->frame_rate = frame_rate;
+	if (sd->frame_rate >= 30)
+		fr = 0;
+	else if (sd->frame_rate >= 25)
+		fr = 1;
+	else if (sd->frame_rate >= 20)
+		fr = 2;
+	else if (sd->frame_rate >= 15)
+		fr = 3;
+	else if (sd->frame_rate >= 10)
+		fr = 4;
+	else
+		fr = 5;
+	reg_w(sd, 0xa4, fr_tb[sd->gspca_dev.curr_mode][fr][0]);
+	reg_w(sd, 0x23, fr_tb[sd->gspca_dev.curr_mode][fr][1]);
+	clock = fr_tb[sd->gspca_dev.curr_mode][fr][2];
+	if (sd->sensor == SEN_OV7660)
+		clock |= 0x80;		/* enable double clock */
+	ov518_i2c_w(sd, OV7670_R11_CLKRC, clock);
+}
+
 /* this function is called at probe time */
 static int sd_config(struct gspca_dev *gspca_dev,
 			const struct usb_device_id *id)
@@ -3118,6 +3356,34 @@
 	case SEN_OV7648:
 		write_i2c_regvals(sd, norm_7640, ARRAY_SIZE(norm_7640));
 		break;
+	case SEN_OV7660:
+		i2c_w(sd, OV7670_R12_COM7, OV7670_COM7_RESET);
+		msleep(14);
+		reg_w(sd, OV519_R57_SNAPSHOT, 0x23);
+		write_regvals(sd, init_519_ov7660,
+				ARRAY_SIZE(init_519_ov7660));
+		write_i2c_regvals(sd, norm_7660, ARRAY_SIZE(norm_7660));
+		sd->gspca_dev.curr_mode = 1;	/* 640x480 */
+		sd->frame_rate = 15;
+		ov519_set_mode(sd);
+		ov519_set_fr(sd);
+		sd->ctrls[COLORS].max = 4;	/* 0..4 */
+		sd->ctrls[COLORS].val =
+			sd->ctrls[COLORS].def = 2;
+		setcolors(gspca_dev);
+		sd->ctrls[CONTRAST].max = 6;	/* 0..6 */
+		sd->ctrls[CONTRAST].val =
+			sd->ctrls[CONTRAST].def = 3;
+		setcontrast(gspca_dev);
+		sd->ctrls[BRIGHTNESS].max = 6;	/* 0..6 */
+		sd->ctrls[BRIGHTNESS].val =
+			sd->ctrls[BRIGHTNESS].def = 3;
+		setbrightness(gspca_dev);
+		sd_reset_snapshot(gspca_dev);
+		ov51x_restart(sd);
+		ov51x_stop(sd);			/* not in win traces */
+		ov51x_led_control(sd, 0);
+		break;
 	case SEN_OV7670:
 		sd->ctrls[FREQ].max = 3;	/* auto */
 		sd->ctrls[FREQ].def = 3;
@@ -3431,16 +3697,21 @@
 	};
 
 	/******** Set the mode ********/
-	if (sd->sensor != SEN_OV7670) {
+	switch (sd->sensor) {
+	default:
 		write_regvals(sd, mode_init_519, ARRAY_SIZE(mode_init_519));
 		if (sd->sensor == SEN_OV7640 ||
 		    sd->sensor == SEN_OV7648) {
 			/* Select 8-bit input mode */
 			reg_w_mask(sd, OV519_R20_DFR, 0x10, 0x10);
 		}
-	} else {
+		break;
+	case SEN_OV7660:
+		return;		/* done by ov519_set_mode/fr() */
+	case SEN_OV7670:
 		write_regvals(sd, mode_init_519_ov7670,
 				ARRAY_SIZE(mode_init_519_ov7670));
+		break;
 	}
 
 	reg_w(sd, OV519_R10_H_SIZE,	sd->gspca_dev.width >> 4);
@@ -3682,6 +3953,7 @@
 	i2c_w(sd, 0x11, sd->clockdiv);
 }
 
+/* this function works for bridge ov519 and sensors ov7660 and ov7670 only */
 static void sethvflip(struct gspca_dev *gspca_dev)
 {
 	struct sd *sd = (struct sd *) gspca_dev;
@@ -3703,11 +3975,18 @@
 	int hwsbase, hwebase, vwsbase, vwebase, hwscale, vwscale;
 
 	/* mode setup is fully handled in mode_init_ov_sensor_regs for these */
-	if (sd->sensor == SEN_OV2610 || sd->sensor == SEN_OV3610 ||
-	    sd->sensor == SEN_OV7670) {
+	switch (sd->sensor) {
+	case SEN_OV2610:
+	case SEN_OV3610:
+	case SEN_OV7670:
 		mode_init_ov_sensor_regs(sd);
 		return;
+	case SEN_OV7660:
+		ov519_set_mode(sd);
+		ov519_set_fr(sd);
+		return;
 	}
+
 	gspca_dev = &sd->gspca_dev;
 	qvga = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv & 1;
 	crop = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv & 2;
@@ -4101,6 +4380,22 @@
 {
 	struct sd *sd = (struct sd *) gspca_dev;
 	int val;
+	static const struct ov_i2c_regvals brit_7660[][7] = {
+		{{0x0f, 0x6a}, {0x24, 0x40}, {0x25, 0x2b}, {0x26, 0x90},
+			{0x27, 0xe0}, {0x28, 0xe0}, {0x2c, 0xe0}},
+		{{0x0f, 0x6a}, {0x24, 0x50}, {0x25, 0x40}, {0x26, 0xa1},
+			{0x27, 0xc0}, {0x28, 0xc0}, {0x2c, 0xc0}},
+		{{0x0f, 0x6a}, {0x24, 0x68}, {0x25, 0x58}, {0x26, 0xc2},
+			{0x27, 0xa0}, {0x28, 0xa0}, {0x2c, 0xa0}},
+		{{0x0f, 0x6a}, {0x24, 0x70}, {0x25, 0x68}, {0x26, 0xd3},
+			{0x27, 0x80}, {0x28, 0x80}, {0x2c, 0x80}},
+		{{0x0f, 0x6a}, {0x24, 0x80}, {0x25, 0x70}, {0x26, 0xd3},
+			{0x27, 0x20}, {0x28, 0x20}, {0x2c, 0x20}},
+		{{0x0f, 0x6a}, {0x24, 0x88}, {0x25, 0x78}, {0x26, 0xd3},
+			{0x27, 0x40}, {0x28, 0x40}, {0x2c, 0x40}},
+		{{0x0f, 0x6a}, {0x24, 0x90}, {0x25, 0x80}, {0x26, 0xd4},
+			{0x27, 0x60}, {0x28, 0x60}, {0x2c, 0x60}}
+	};
 
 	val = sd->ctrls[BRIGHTNESS].val;
 	switch (sd->sensor) {
@@ -4120,6 +4415,10 @@
 		if (!sd->ctrls[AUTOBRIGHT].val)
 			i2c_w(sd, OV7610_REG_BRT, val);
 		break;
+	case SEN_OV7660:
+		write_i2c_regvals(sd, brit_7660[val],
+				ARRAY_SIZE(brit_7660[0]));
+		break;
 	case SEN_OV7670:
 /*win trace
  *		i2c_w_mask(sd, OV7670_R13_COM8, 0, OV7670_COM8_AEC); */
@@ -4132,6 +4431,64 @@
 {
 	struct sd *sd = (struct sd *) gspca_dev;
 	int val;
+	static const struct ov_i2c_regvals contrast_7660[][31] = {
+		{{0x6c, 0xf0}, {0x6d, 0xf0}, {0x6e, 0xf8}, {0x6f, 0xa0},
+		 {0x70, 0x58}, {0x71, 0x38}, {0x72, 0x30}, {0x73, 0x30},
+		 {0x74, 0x28}, {0x75, 0x28}, {0x76, 0x24}, {0x77, 0x24},
+		 {0x78, 0x22}, {0x79, 0x28}, {0x7a, 0x2a}, {0x7b, 0x34},
+		 {0x7c, 0x0f}, {0x7d, 0x1e}, {0x7e, 0x3d}, {0x7f, 0x65},
+		 {0x80, 0x70}, {0x81, 0x77}, {0x82, 0x7d}, {0x83, 0x83},
+		 {0x84, 0x88}, {0x85, 0x8d}, {0x86, 0x96}, {0x87, 0x9f},
+		 {0x88, 0xb0}, {0x89, 0xc4}, {0x8a, 0xd9}},
+		{{0x6c, 0xf0}, {0x6d, 0xf0}, {0x6e, 0xf8}, {0x6f, 0x94},
+		 {0x70, 0x58}, {0x71, 0x40}, {0x72, 0x30}, {0x73, 0x30},
+		 {0x74, 0x30}, {0x75, 0x30}, {0x76, 0x2c}, {0x77, 0x24},
+		 {0x78, 0x22}, {0x79, 0x28}, {0x7a, 0x2a}, {0x7b, 0x31},
+		 {0x7c, 0x0f}, {0x7d, 0x1e}, {0x7e, 0x3d}, {0x7f, 0x62},
+		 {0x80, 0x6d}, {0x81, 0x75}, {0x82, 0x7b}, {0x83, 0x81},
+		 {0x84, 0x87}, {0x85, 0x8d}, {0x86, 0x98}, {0x87, 0xa1},
+		 {0x88, 0xb2}, {0x89, 0xc6}, {0x8a, 0xdb}},
+		{{0x6c, 0xf0}, {0x6d, 0xf0}, {0x6e, 0xf0}, {0x6f, 0x84},
+		 {0x70, 0x58}, {0x71, 0x48}, {0x72, 0x40}, {0x73, 0x40},
+		 {0x74, 0x28}, {0x75, 0x28}, {0x76, 0x28}, {0x77, 0x24},
+		 {0x78, 0x26}, {0x79, 0x28}, {0x7a, 0x28}, {0x7b, 0x34},
+		 {0x7c, 0x0f}, {0x7d, 0x1e}, {0x7e, 0x3c}, {0x7f, 0x5d},
+		 {0x80, 0x68}, {0x81, 0x71}, {0x82, 0x79}, {0x83, 0x81},
+		 {0x84, 0x86}, {0x85, 0x8b}, {0x86, 0x95}, {0x87, 0x9e},
+		 {0x88, 0xb1}, {0x89, 0xc5}, {0x8a, 0xd9}},
+		{{0x6c, 0xf0}, {0x6d, 0xf0}, {0x6e, 0xf0}, {0x6f, 0x70},
+		 {0x70, 0x58}, {0x71, 0x58}, {0x72, 0x48}, {0x73, 0x48},
+		 {0x74, 0x38}, {0x75, 0x40}, {0x76, 0x34}, {0x77, 0x34},
+		 {0x78, 0x2e}, {0x79, 0x28}, {0x7a, 0x24}, {0x7b, 0x22},
+		 {0x7c, 0x0f}, {0x7d, 0x1e}, {0x7e, 0x3c}, {0x7f, 0x58},
+		 {0x80, 0x63}, {0x81, 0x6e}, {0x82, 0x77}, {0x83, 0x80},
+		 {0x84, 0x87}, {0x85, 0x8f}, {0x86, 0x9c}, {0x87, 0xa9},
+		 {0x88, 0xc0}, {0x89, 0xd4}, {0x8a, 0xe6}},
+		{{0x6c, 0xa0}, {0x6d, 0xf0}, {0x6e, 0x90}, {0x6f, 0x80},
+		 {0x70, 0x70}, {0x71, 0x80}, {0x72, 0x60}, {0x73, 0x60},
+		 {0x74, 0x58}, {0x75, 0x60}, {0x76, 0x4c}, {0x77, 0x38},
+		 {0x78, 0x38}, {0x79, 0x2a}, {0x7a, 0x20}, {0x7b, 0x0e},
+		 {0x7c, 0x0a}, {0x7d, 0x14}, {0x7e, 0x26}, {0x7f, 0x46},
+		 {0x80, 0x54}, {0x81, 0x64}, {0x82, 0x70}, {0x83, 0x7c},
+		 {0x84, 0x87}, {0x85, 0x93}, {0x86, 0xa6}, {0x87, 0xb4},
+		 {0x88, 0xd0}, {0x89, 0xe5}, {0x8a, 0xf5}},
+		{{0x6c, 0x60}, {0x6d, 0x80}, {0x6e, 0x60}, {0x6f, 0x80},
+		 {0x70, 0x80}, {0x71, 0x80}, {0x72, 0x88}, {0x73, 0x30},
+		 {0x74, 0x70}, {0x75, 0x68}, {0x76, 0x64}, {0x77, 0x50},
+		 {0x78, 0x3c}, {0x79, 0x22}, {0x7a, 0x10}, {0x7b, 0x08},
+		 {0x7c, 0x06}, {0x7d, 0x0e}, {0x7e, 0x1a}, {0x7f, 0x3a},
+		 {0x80, 0x4a}, {0x81, 0x5a}, {0x82, 0x6b}, {0x83, 0x7b},
+		 {0x84, 0x89}, {0x85, 0x96}, {0x86, 0xaf}, {0x87, 0xc3},
+		 {0x88, 0xe1}, {0x89, 0xf2}, {0x8a, 0xfa}},
+		{{0x6c, 0x20}, {0x6d, 0x40}, {0x6e, 0x20}, {0x6f, 0x60},
+		 {0x70, 0x88}, {0x71, 0xc8}, {0x72, 0xc0}, {0x73, 0xb8},
+		 {0x74, 0xa8}, {0x75, 0xb8}, {0x76, 0x80}, {0x77, 0x5c},
+		 {0x78, 0x26}, {0x79, 0x10}, {0x7a, 0x08}, {0x7b, 0x04},
+		 {0x7c, 0x02}, {0x7d, 0x06}, {0x7e, 0x0a}, {0x7f, 0x22},
+		 {0x80, 0x33}, {0x81, 0x4c}, {0x82, 0x64}, {0x83, 0x7b},
+		 {0x84, 0x90}, {0x85, 0xa7}, {0x86, 0xc7}, {0x87, 0xde},
+		 {0x88, 0xf1}, {0x89, 0xf9}, {0x8a, 0xfd}},
+	};
 
 	val = sd->ctrls[CONTRAST].val;
 	switch (sd->sensor) {
@@ -4163,6 +4520,10 @@
 		i2c_w(sd, 0x64, ctab[val >> 4]);
 		break;
 	    }
+	case SEN_OV7660:
+		write_i2c_regvals(sd, contrast_7660[val],
+					ARRAY_SIZE(contrast_7660[0]));
+		break;
 	case SEN_OV7670:
 		/* check that this isn't just the same as ov7610 */
 		i2c_w(sd, OV7670_R56_CONTRAS, val >> 1);
@@ -4174,6 +4535,18 @@
 {
 	struct sd *sd = (struct sd *) gspca_dev;
 	int val;
+	static const struct ov_i2c_regvals colors_7660[][6] = {
+		{{0x4f, 0x28}, {0x50, 0x2a}, {0x51, 0x02}, {0x52, 0x0a},
+		 {0x53, 0x19}, {0x54, 0x23}},
+		{{0x4f, 0x47}, {0x50, 0x4a}, {0x51, 0x03}, {0x52, 0x11},
+		 {0x53, 0x2c}, {0x54, 0x3e}},
+		{{0x4f, 0x66}, {0x50, 0x6b}, {0x51, 0x05}, {0x52, 0x19},
+		 {0x53, 0x40}, {0x54, 0x59}},
+		{{0x4f, 0x84}, {0x50, 0x8b}, {0x51, 0x06}, {0x52, 0x20},
+		 {0x53, 0x53}, {0x54, 0x73}},
+		{{0x4f, 0xa3}, {0x50, 0xab}, {0x51, 0x08}, {0x52, 0x28},
+		 {0x53, 0x66}, {0x54, 0x8e}},
+	};
 
 	val = sd->ctrls[COLORS].val;
 	switch (sd->sensor) {
@@ -4197,6 +4570,10 @@
 	case SEN_OV7648:
 		i2c_w(sd, OV7610_REG_SAT, val & 0xf0);
 		break;
+	case SEN_OV7660:
+		write_i2c_regvals(sd, colors_7660[val],
+					ARRAY_SIZE(colors_7660[0]));
+		break;
 	case SEN_OV7670:
 		/* supported later once I work out how to do it
 		 * transparently fail now! */
@@ -4214,7 +4591,8 @@
 
 static void setfreq_i(struct sd *sd)
 {
-	if (sd->sensor == SEN_OV7670) {
+	if (sd->sensor == SEN_OV7660
+	 || sd->sensor == SEN_OV7670) {
 		switch (sd->ctrls[FREQ].val) {
 		case 0: /* Banding filter disabled */
 			i2c_w_mask(sd, OV7670_R13_COM8, 0, OV7670_COM8_BFILT);