114ccffa3SMauro Carvalho Chehab 
20c0d06caSMauro Carvalho Chehab /*
30c0d06caSMauro Carvalho Chehab  * Driver for the ov9650 sensor
40c0d06caSMauro Carvalho Chehab  *
50c0d06caSMauro Carvalho Chehab  * Copyright (C) 2008 Erik Andrén
60c0d06caSMauro Carvalho Chehab  * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
70c0d06caSMauro Carvalho Chehab  * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
80c0d06caSMauro Carvalho Chehab  *
90c0d06caSMauro Carvalho Chehab  * Portions of code to USB interface and ALi driver software,
100c0d06caSMauro Carvalho Chehab  * Copyright (c) 2006 Willem Duinker
110c0d06caSMauro Carvalho Chehab  * v4l2 interface modeled after the V4L2 driver
120c0d06caSMauro Carvalho Chehab  * for SN9C10x PC Camera Controllers
130c0d06caSMauro Carvalho Chehab  *
140c0d06caSMauro Carvalho Chehab  * This program is free software; you can redistribute it and/or
150c0d06caSMauro Carvalho Chehab  * modify it under the terms of the GNU General Public License as
160c0d06caSMauro Carvalho Chehab  * published by the Free Software Foundation, version 2.
170c0d06caSMauro Carvalho Chehab  *
180c0d06caSMauro Carvalho Chehab  */
190c0d06caSMauro Carvalho Chehab 
200c0d06caSMauro Carvalho Chehab #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
210c0d06caSMauro Carvalho Chehab 
220c0d06caSMauro Carvalho Chehab #include "m5602_ov9650.h"
230c0d06caSMauro Carvalho Chehab 
24c84e412fSHans de Goede static int ov9650_s_ctrl(struct v4l2_ctrl *ctrl);
25c84e412fSHans de Goede static void ov9650_dump_registers(struct sd *sd);
260c0d06caSMauro Carvalho Chehab 
2714ccffa3SMauro Carvalho Chehab static const unsigned char preinit_ov9650[][3] = {
2814ccffa3SMauro Carvalho Chehab 	/* [INITCAM] */
2914ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02},
3014ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0},
3114ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
3214ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
3314ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
3414ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00},
3514ccffa3SMauro Carvalho Chehab 
3614ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x08},
3714ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
3814ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
3914ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
4014ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
4114ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00},
4214ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
4314ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a},
4414ccffa3SMauro Carvalho Chehab 	/* Reset chip */
4514ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM7, OV9650_REGISTER_RESET},
4614ccffa3SMauro Carvalho Chehab 	/* Enable double clock */
4714ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_CLKRC, 0x80},
4814ccffa3SMauro Carvalho Chehab 	/* Do something out of spec with the power */
4914ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_OFON, 0x40}
5014ccffa3SMauro Carvalho Chehab };
5114ccffa3SMauro Carvalho Chehab 
5214ccffa3SMauro Carvalho Chehab static const unsigned char init_ov9650[][3] = {
5314ccffa3SMauro Carvalho Chehab 	/* [INITCAM] */
5414ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02},
5514ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0},
5614ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
5714ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
5814ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
5914ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00},
6014ccffa3SMauro Carvalho Chehab 
6114ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x08},
6214ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
6314ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
6414ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
6514ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
6614ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00},
6714ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
6814ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a},
6914ccffa3SMauro Carvalho Chehab 
7014ccffa3SMauro Carvalho Chehab 	/* Reset chip */
7114ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM7, OV9650_REGISTER_RESET},
7214ccffa3SMauro Carvalho Chehab 	/* One extra reset is needed in order to make the sensor behave
7314ccffa3SMauro Carvalho Chehab 	   properly when resuming from ram, could be a timing issue */
7414ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM7, OV9650_REGISTER_RESET},
7514ccffa3SMauro Carvalho Chehab 
7614ccffa3SMauro Carvalho Chehab 	/* Enable double clock */
7714ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_CLKRC, 0x80},
7814ccffa3SMauro Carvalho Chehab 	/* Do something out of spec with the power */
7914ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_OFON, 0x40},
8014ccffa3SMauro Carvalho Chehab 
8114ccffa3SMauro Carvalho Chehab 	/* Set fast AGC/AEC algorithm with unlimited step size */
8214ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM8, OV9650_FAST_AGC_AEC |
8314ccffa3SMauro Carvalho Chehab 			      OV9650_AEC_UNLIM_STEP_SIZE},
8414ccffa3SMauro Carvalho Chehab 
8514ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_CHLF, 0x10},
8614ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_ARBLM, 0xbf},
8714ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_ACOM38, 0x81},
8814ccffa3SMauro Carvalho Chehab 	/* Turn off color matrix coefficient double option */
8914ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM16, 0x00},
9014ccffa3SMauro Carvalho Chehab 	/* Enable color matrix for RGB/YUV, Delay Y channel,
9114ccffa3SMauro Carvalho Chehab 	set output Y/UV delay to 1 */
9214ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM13, 0x19},
9314ccffa3SMauro Carvalho Chehab 	/* Enable digital BLC, Set output mode to U Y V Y */
9414ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_TSLB, 0x0c},
9514ccffa3SMauro Carvalho Chehab 	/* Limit the AGC/AEC stable upper region */
9614ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM24, 0x00},
9714ccffa3SMauro Carvalho Chehab 	/* Enable HREF and some out of spec things */
9814ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM12, 0x73},
9914ccffa3SMauro Carvalho Chehab 	/* Set all DBLC offset signs to positive and
10014ccffa3SMauro Carvalho Chehab 	do some out of spec stuff */
10114ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_DBLC1, 0xdf},
10214ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM21, 0x06},
10314ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_RSVD35, 0x91},
10414ccffa3SMauro Carvalho Chehab 	/* Necessary, no camera stream without it */
10514ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_RSVD16, 0x06},
10614ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_RSVD94, 0x99},
10714ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_RSVD95, 0x99},
10814ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_RSVD96, 0x04},
10914ccffa3SMauro Carvalho Chehab 	/* Enable full range output */
11014ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM15, 0x0},
11114ccffa3SMauro Carvalho Chehab 	/* Enable HREF at optical black, enable ADBLC bias,
11214ccffa3SMauro Carvalho Chehab 	enable ADBLC, reset timings at format change */
11314ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM6, 0x4b},
11414ccffa3SMauro Carvalho Chehab 	/* Subtract 32 from the B channel bias */
11514ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_BBIAS, 0xa0},
11614ccffa3SMauro Carvalho Chehab 	/* Subtract 32 from the Gb channel bias */
11714ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_GbBIAS, 0xa0},
11814ccffa3SMauro Carvalho Chehab 	/* Do not bypass the analog BLC and to some out of spec stuff */
11914ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_Gr_COM, 0x00},
12014ccffa3SMauro Carvalho Chehab 	/* Subtract 32 from the R channel bias */
12114ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_RBIAS, 0xa0},
12214ccffa3SMauro Carvalho Chehab 	/* Subtract 32 from the R channel bias */
12314ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_RBIAS, 0x0},
12414ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM26, 0x80},
12514ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_ACOMA9, 0x98},
12614ccffa3SMauro Carvalho Chehab 	/* Set the AGC/AEC stable region upper limit */
12714ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_AEW, 0x68},
12814ccffa3SMauro Carvalho Chehab 	/* Set the AGC/AEC stable region lower limit */
12914ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_AEB, 0x5c},
13014ccffa3SMauro Carvalho Chehab 	/* Set the high and low limit nibbles to 3 */
13114ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_VPT, 0xc3},
13214ccffa3SMauro Carvalho Chehab 	/* Set the Automatic Gain Ceiling (AGC) to 128x,
13314ccffa3SMauro Carvalho Chehab 	drop VSYNC at frame drop,
13414ccffa3SMauro Carvalho Chehab 	limit exposure timing,
13514ccffa3SMauro Carvalho Chehab 	drop frame when the AEC step is larger than the exposure gap */
13614ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM9, 0x6e},
13714ccffa3SMauro Carvalho Chehab 	/* Set VSYNC negative, Set RESET to SLHS (slave mode horizontal sync)
13814ccffa3SMauro Carvalho Chehab 	and set PWDN to SLVS (slave mode vertical sync) */
13914ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM10, 0x42},
14014ccffa3SMauro Carvalho Chehab 	/* Set horizontal column start high to default value */
14114ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_HSTART, 0x1a}, /* 210 */
14214ccffa3SMauro Carvalho Chehab 	/* Set horizontal column end */
14314ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_HSTOP, 0xbf}, /* 1534 */
14414ccffa3SMauro Carvalho Chehab 	/* Complementing register to the two writes above */
14514ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_HREF, 0xb2},
14614ccffa3SMauro Carvalho Chehab 	/* Set vertical row start high bits */
14714ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_VSTRT, 0x02},
14814ccffa3SMauro Carvalho Chehab 	/* Set vertical row end low bits */
14914ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_VSTOP, 0x7e},
15014ccffa3SMauro Carvalho Chehab 	/* Set complementing vertical frame control */
15114ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_VREF, 0x10},
15214ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_ADC, 0x04},
15314ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_HV, 0x40},
15414ccffa3SMauro Carvalho Chehab 
15514ccffa3SMauro Carvalho Chehab 	/* Enable denoise, and white-pixel erase */
15614ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM22, OV9650_DENOISE_ENABLE |
15714ccffa3SMauro Carvalho Chehab 		 OV9650_WHITE_PIXEL_ENABLE |
15814ccffa3SMauro Carvalho Chehab 		 OV9650_WHITE_PIXEL_OPTION},
15914ccffa3SMauro Carvalho Chehab 
16014ccffa3SMauro Carvalho Chehab 	/* Enable VARIOPIXEL */
16114ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM3, OV9650_VARIOPIXEL},
16214ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM4, OV9650_QVGA_VARIOPIXEL},
16314ccffa3SMauro Carvalho Chehab 
16414ccffa3SMauro Carvalho Chehab 	/* Put the sensor in soft sleep mode */
16514ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM2, OV9650_SOFT_SLEEP | OV9650_OUTPUT_DRIVE_2X},
16614ccffa3SMauro Carvalho Chehab };
16714ccffa3SMauro Carvalho Chehab 
16814ccffa3SMauro Carvalho Chehab static const unsigned char res_init_ov9650[][3] = {
16914ccffa3SMauro Carvalho Chehab 	{SENSOR, OV9650_COM2, OV9650_OUTPUT_DRIVE_2X},
17014ccffa3SMauro Carvalho Chehab 
17114ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x82},
17214ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_LINE_OF_FRAME_L, 0x00},
17314ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82},
17414ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_PIX_OF_LINE_L, 0x00},
17514ccffa3SMauro Carvalho Chehab 	{BRIDGE, M5602_XB_SIG_INI, 0x01}
17614ccffa3SMauro Carvalho Chehab };
17714ccffa3SMauro Carvalho Chehab 
1780c0d06caSMauro Carvalho Chehab /* Vertically and horizontally flips the image if matched, needed for machines
1790c0d06caSMauro Carvalho Chehab    where the sensor is mounted upside down */
1800c0d06caSMauro Carvalho Chehab static
1810c0d06caSMauro Carvalho Chehab     const
1820c0d06caSMauro Carvalho Chehab 	struct dmi_system_id ov9650_flip_dmi_table[] = {
1830c0d06caSMauro Carvalho Chehab 	{
1840c0d06caSMauro Carvalho Chehab 		.ident = "ASUS A6Ja",
1850c0d06caSMauro Carvalho Chehab 		.matches = {
1860c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
1870c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "A6J")
1880c0d06caSMauro Carvalho Chehab 		}
1890c0d06caSMauro Carvalho Chehab 	},
1900c0d06caSMauro Carvalho Chehab 	{
1910c0d06caSMauro Carvalho Chehab 		.ident = "ASUS A6JC",
1920c0d06caSMauro Carvalho Chehab 		.matches = {
1930c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
1940c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "A6JC")
1950c0d06caSMauro Carvalho Chehab 		}
1960c0d06caSMauro Carvalho Chehab 	},
1970c0d06caSMauro Carvalho Chehab 	{
1980c0d06caSMauro Carvalho Chehab 		.ident = "ASUS A6K",
1990c0d06caSMauro Carvalho Chehab 		.matches = {
2000c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
2010c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "A6K")
2020c0d06caSMauro Carvalho Chehab 		}
2030c0d06caSMauro Carvalho Chehab 	},
2040c0d06caSMauro Carvalho Chehab 	{
2050c0d06caSMauro Carvalho Chehab 		.ident = "ASUS A6Kt",
2060c0d06caSMauro Carvalho Chehab 		.matches = {
2070c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
2080c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "A6Kt")
2090c0d06caSMauro Carvalho Chehab 		}
2100c0d06caSMauro Carvalho Chehab 	},
2110c0d06caSMauro Carvalho Chehab 	{
2120c0d06caSMauro Carvalho Chehab 		.ident = "ASUS A6VA",
2130c0d06caSMauro Carvalho Chehab 		.matches = {
2140c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
2150c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "A6VA")
2160c0d06caSMauro Carvalho Chehab 		}
2170c0d06caSMauro Carvalho Chehab 	},
2180c0d06caSMauro Carvalho Chehab 	{
2190c0d06caSMauro Carvalho Chehab 
2200c0d06caSMauro Carvalho Chehab 		.ident = "ASUS A6VC",
2210c0d06caSMauro Carvalho Chehab 		.matches = {
2220c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
2230c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "A6VC")
2240c0d06caSMauro Carvalho Chehab 		}
2250c0d06caSMauro Carvalho Chehab 	},
2260c0d06caSMauro Carvalho Chehab 	{
2270c0d06caSMauro Carvalho Chehab 		.ident = "ASUS A6VM",
2280c0d06caSMauro Carvalho Chehab 		.matches = {
2290c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
2300c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "A6VM")
2310c0d06caSMauro Carvalho Chehab 		}
2320c0d06caSMauro Carvalho Chehab 	},
2330c0d06caSMauro Carvalho Chehab 	{
2340c0d06caSMauro Carvalho Chehab 		.ident = "ASUS A7V",
2350c0d06caSMauro Carvalho Chehab 		.matches = {
2360c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
2370c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "A7V")
2380c0d06caSMauro Carvalho Chehab 		}
2390c0d06caSMauro Carvalho Chehab 	},
2400c0d06caSMauro Carvalho Chehab 	{
2410c0d06caSMauro Carvalho Chehab 		.ident = "Alienware Aurora m9700",
2420c0d06caSMauro Carvalho Chehab 		.matches = {
2430c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_SYS_VENDOR, "alienware"),
2440c0d06caSMauro Carvalho Chehab 			DMI_MATCH(DMI_PRODUCT_NAME, "Aurora m9700")
2450c0d06caSMauro Carvalho Chehab 		}
2460c0d06caSMauro Carvalho Chehab 	},
2470c0d06caSMauro Carvalho Chehab 	{}
2480c0d06caSMauro Carvalho Chehab };
2490c0d06caSMauro Carvalho Chehab 
2500c0d06caSMauro Carvalho Chehab static struct v4l2_pix_format ov9650_modes[] = {
2510c0d06caSMauro Carvalho Chehab 	{
2520c0d06caSMauro Carvalho Chehab 		176,
2530c0d06caSMauro Carvalho Chehab 		144,
2540c0d06caSMauro Carvalho Chehab 		V4L2_PIX_FMT_SBGGR8,
2550c0d06caSMauro Carvalho Chehab 		V4L2_FIELD_NONE,
2560c0d06caSMauro Carvalho Chehab 		.sizeimage =
2570c0d06caSMauro Carvalho Chehab 			176 * 144,
2580c0d06caSMauro Carvalho Chehab 		.bytesperline = 176,
2590c0d06caSMauro Carvalho Chehab 		.colorspace = V4L2_COLORSPACE_SRGB,
2600c0d06caSMauro Carvalho Chehab 		.priv = 9
2610c0d06caSMauro Carvalho Chehab 	}, {
2620c0d06caSMauro Carvalho Chehab 		320,
2630c0d06caSMauro Carvalho Chehab 		240,
2640c0d06caSMauro Carvalho Chehab 		V4L2_PIX_FMT_SBGGR8,
2650c0d06caSMauro Carvalho Chehab 		V4L2_FIELD_NONE,
2660c0d06caSMauro Carvalho Chehab 		.sizeimage =
2670c0d06caSMauro Carvalho Chehab 			320 * 240,
2680c0d06caSMauro Carvalho Chehab 		.bytesperline = 320,
2690c0d06caSMauro Carvalho Chehab 		.colorspace = V4L2_COLORSPACE_SRGB,
2700c0d06caSMauro Carvalho Chehab 		.priv = 8
2710c0d06caSMauro Carvalho Chehab 	}, {
2720c0d06caSMauro Carvalho Chehab 		352,
2730c0d06caSMauro Carvalho Chehab 		288,
2740c0d06caSMauro Carvalho Chehab 		V4L2_PIX_FMT_SBGGR8,
2750c0d06caSMauro Carvalho Chehab 		V4L2_FIELD_NONE,
2760c0d06caSMauro Carvalho Chehab 		.sizeimage =
2770c0d06caSMauro Carvalho Chehab 			352 * 288,
2780c0d06caSMauro Carvalho Chehab 		.bytesperline = 352,
2790c0d06caSMauro Carvalho Chehab 		.colorspace = V4L2_COLORSPACE_SRGB,
2800c0d06caSMauro Carvalho Chehab 		.priv = 9
2810c0d06caSMauro Carvalho Chehab 	}, {
2820c0d06caSMauro Carvalho Chehab 		640,
2830c0d06caSMauro Carvalho Chehab 		480,
2840c0d06caSMauro Carvalho Chehab 		V4L2_PIX_FMT_SBGGR8,
2850c0d06caSMauro Carvalho Chehab 		V4L2_FIELD_NONE,
2860c0d06caSMauro Carvalho Chehab 		.sizeimage =
2870c0d06caSMauro Carvalho Chehab 			640 * 480,
2880c0d06caSMauro Carvalho Chehab 		.bytesperline = 640,
2890c0d06caSMauro Carvalho Chehab 		.colorspace = V4L2_COLORSPACE_SRGB,
2900c0d06caSMauro Carvalho Chehab 		.priv = 9
2910c0d06caSMauro Carvalho Chehab 	}
2920c0d06caSMauro Carvalho Chehab };
2930c0d06caSMauro Carvalho Chehab 
294c84e412fSHans de Goede static const struct v4l2_ctrl_ops ov9650_ctrl_ops = {
295c84e412fSHans de Goede 	.s_ctrl = ov9650_s_ctrl,
296c84e412fSHans de Goede };
2970c0d06caSMauro Carvalho Chehab 
2980c0d06caSMauro Carvalho Chehab int ov9650_probe(struct sd *sd)
2990c0d06caSMauro Carvalho Chehab {
3000c0d06caSMauro Carvalho Chehab 	int err = 0;
3010c0d06caSMauro Carvalho Chehab 	u8 prod_id = 0, ver_id = 0, i;
302c93396e1STheodore Kilgore 	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
3030c0d06caSMauro Carvalho Chehab 
3040c0d06caSMauro Carvalho Chehab 	if (force_sensor) {
3050c0d06caSMauro Carvalho Chehab 		if (force_sensor == OV9650_SENSOR) {
3060c0d06caSMauro Carvalho Chehab 			pr_info("Forcing an %s sensor\n", ov9650.name);
3070c0d06caSMauro Carvalho Chehab 			goto sensor_found;
3080c0d06caSMauro Carvalho Chehab 		}
3090c0d06caSMauro Carvalho Chehab 		/* If we want to force another sensor,
3100c0d06caSMauro Carvalho Chehab 		   don't try to probe this one */
3110c0d06caSMauro Carvalho Chehab 		return -ENODEV;
3120c0d06caSMauro Carvalho Chehab 	}
3130c0d06caSMauro Carvalho Chehab 
31437d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_PROBE, "Probing for an ov9650 sensor\n");
3150c0d06caSMauro Carvalho Chehab 
3160c0d06caSMauro Carvalho Chehab 	/* Run the pre-init before probing the sensor */
3170c0d06caSMauro Carvalho Chehab 	for (i = 0; i < ARRAY_SIZE(preinit_ov9650) && !err; i++) {
3180c0d06caSMauro Carvalho Chehab 		u8 data = preinit_ov9650[i][2];
3190c0d06caSMauro Carvalho Chehab 		if (preinit_ov9650[i][0] == SENSOR)
3200c0d06caSMauro Carvalho Chehab 			err = m5602_write_sensor(sd,
3210c0d06caSMauro Carvalho Chehab 				preinit_ov9650[i][1], &data, 1);
3220c0d06caSMauro Carvalho Chehab 		else
3230c0d06caSMauro Carvalho Chehab 			err = m5602_write_bridge(sd,
3240c0d06caSMauro Carvalho Chehab 				preinit_ov9650[i][1], data);
3250c0d06caSMauro Carvalho Chehab 	}
3260c0d06caSMauro Carvalho Chehab 
3270c0d06caSMauro Carvalho Chehab 	if (err < 0)
3280c0d06caSMauro Carvalho Chehab 		return err;
3290c0d06caSMauro Carvalho Chehab 
3300c0d06caSMauro Carvalho Chehab 	if (m5602_read_sensor(sd, OV9650_PID, &prod_id, 1))
3310c0d06caSMauro Carvalho Chehab 		return -ENODEV;
3320c0d06caSMauro Carvalho Chehab 
3330c0d06caSMauro Carvalho Chehab 	if (m5602_read_sensor(sd, OV9650_VER, &ver_id, 1))
3340c0d06caSMauro Carvalho Chehab 		return -ENODEV;
3350c0d06caSMauro Carvalho Chehab 
3360c0d06caSMauro Carvalho Chehab 	if ((prod_id == 0x96) && (ver_id == 0x52)) {
3370c0d06caSMauro Carvalho Chehab 		pr_info("Detected an ov9650 sensor\n");
3380c0d06caSMauro Carvalho Chehab 		goto sensor_found;
3390c0d06caSMauro Carvalho Chehab 	}
3400c0d06caSMauro Carvalho Chehab 	return -ENODEV;
3410c0d06caSMauro Carvalho Chehab 
3420c0d06caSMauro Carvalho Chehab sensor_found:
3430c0d06caSMauro Carvalho Chehab 	sd->gspca_dev.cam.cam_mode = ov9650_modes;
3440c0d06caSMauro Carvalho Chehab 	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(ov9650_modes);
3450c0d06caSMauro Carvalho Chehab 
3460c0d06caSMauro Carvalho Chehab 	return 0;
3470c0d06caSMauro Carvalho Chehab }
3480c0d06caSMauro Carvalho Chehab 
3490c0d06caSMauro Carvalho Chehab int ov9650_init(struct sd *sd)
3500c0d06caSMauro Carvalho Chehab {
3510c0d06caSMauro Carvalho Chehab 	int i, err = 0;
3520c0d06caSMauro Carvalho Chehab 	u8 data;
3530c0d06caSMauro Carvalho Chehab 
3540c0d06caSMauro Carvalho Chehab 	if (dump_sensor)
3550c0d06caSMauro Carvalho Chehab 		ov9650_dump_registers(sd);
3560c0d06caSMauro Carvalho Chehab 
3570c0d06caSMauro Carvalho Chehab 	for (i = 0; i < ARRAY_SIZE(init_ov9650) && !err; i++) {
3580c0d06caSMauro Carvalho Chehab 		data = init_ov9650[i][2];
3590c0d06caSMauro Carvalho Chehab 		if (init_ov9650[i][0] == SENSOR)
3600c0d06caSMauro Carvalho Chehab 			err = m5602_write_sensor(sd, init_ov9650[i][1],
3610c0d06caSMauro Carvalho Chehab 						  &data, 1);
3620c0d06caSMauro Carvalho Chehab 		else
3630c0d06caSMauro Carvalho Chehab 			err = m5602_write_bridge(sd, init_ov9650[i][1], data);
3640c0d06caSMauro Carvalho Chehab 	}
3650c0d06caSMauro Carvalho Chehab 
366c84e412fSHans de Goede 	return 0;
367c84e412fSHans de Goede }
3680c0d06caSMauro Carvalho Chehab 
369c84e412fSHans de Goede int ov9650_init_controls(struct sd *sd)
370c84e412fSHans de Goede {
371c84e412fSHans de Goede 	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
3720c0d06caSMauro Carvalho Chehab 
373c84e412fSHans de Goede 	sd->gspca_dev.vdev.ctrl_handler = hdl;
374c84e412fSHans de Goede 	v4l2_ctrl_handler_init(hdl, 9);
3750c0d06caSMauro Carvalho Chehab 
376c84e412fSHans de Goede 	sd->auto_white_bal = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops,
377c84e412fSHans de Goede 					       V4L2_CID_AUTO_WHITE_BALANCE,
378c84e412fSHans de Goede 					       0, 1, 1, 1);
379c84e412fSHans de Goede 	sd->red_bal = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops,
380c84e412fSHans de Goede 					V4L2_CID_RED_BALANCE, 0, 255, 1,
381c84e412fSHans de Goede 					RED_GAIN_DEFAULT);
382c84e412fSHans de Goede 	sd->blue_bal = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops,
383c84e412fSHans de Goede 					V4L2_CID_BLUE_BALANCE, 0, 255, 1,
384c84e412fSHans de Goede 					BLUE_GAIN_DEFAULT);
3850c0d06caSMauro Carvalho Chehab 
386c84e412fSHans de Goede 	sd->autoexpo = v4l2_ctrl_new_std_menu(hdl, &ov9650_ctrl_ops,
387c84e412fSHans de Goede 			  V4L2_CID_EXPOSURE_AUTO, 1, 0, V4L2_EXPOSURE_AUTO);
388c84e412fSHans de Goede 	sd->expo = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops, V4L2_CID_EXPOSURE,
389c84e412fSHans de Goede 			  0, 0x1ff, 4, EXPOSURE_DEFAULT);
3900c0d06caSMauro Carvalho Chehab 
391c84e412fSHans de Goede 	sd->autogain = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops,
392c84e412fSHans de Goede 					 V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
393c84e412fSHans de Goede 	sd->gain = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops, V4L2_CID_GAIN, 0,
394c84e412fSHans de Goede 				     0x3ff, 1, GAIN_DEFAULT);
3950c0d06caSMauro Carvalho Chehab 
396c84e412fSHans de Goede 	sd->hflip = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops, V4L2_CID_HFLIP,
397c84e412fSHans de Goede 				      0, 1, 1, 0);
398c84e412fSHans de Goede 	sd->vflip = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops, V4L2_CID_VFLIP,
399c84e412fSHans de Goede 				      0, 1, 1, 0);
4000c0d06caSMauro Carvalho Chehab 
401c84e412fSHans de Goede 	if (hdl->error) {
402c84e412fSHans de Goede 		pr_err("Could not initialize controls\n");
403c84e412fSHans de Goede 		return hdl->error;
404c84e412fSHans de Goede 	}
4050c0d06caSMauro Carvalho Chehab 
406c84e412fSHans de Goede 	v4l2_ctrl_auto_cluster(3, &sd->auto_white_bal, 0, false);
407c84e412fSHans de Goede 	v4l2_ctrl_auto_cluster(2, &sd->autoexpo, 0, false);
408c84e412fSHans de Goede 	v4l2_ctrl_auto_cluster(2, &sd->autogain, 0, false);
409c84e412fSHans de Goede 	v4l2_ctrl_cluster(2, &sd->hflip);
410c84e412fSHans de Goede 
411c84e412fSHans de Goede 	return 0;
4120c0d06caSMauro Carvalho Chehab }
4130c0d06caSMauro Carvalho Chehab 
4140c0d06caSMauro Carvalho Chehab int ov9650_start(struct sd *sd)
4150c0d06caSMauro Carvalho Chehab {
4160c0d06caSMauro Carvalho Chehab 	u8 data;
4170c0d06caSMauro Carvalho Chehab 	int i, err = 0;
4180c0d06caSMauro Carvalho Chehab 	struct cam *cam = &sd->gspca_dev.cam;
4190c0d06caSMauro Carvalho Chehab 
4200c0d06caSMauro Carvalho Chehab 	int width = cam->cam_mode[sd->gspca_dev.curr_mode].width;
4210c0d06caSMauro Carvalho Chehab 	int height = cam->cam_mode[sd->gspca_dev.curr_mode].height;
4220c0d06caSMauro Carvalho Chehab 	int ver_offs = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
4230c0d06caSMauro Carvalho Chehab 	int hor_offs = OV9650_LEFT_OFFSET;
424c93396e1STheodore Kilgore 	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
4250c0d06caSMauro Carvalho Chehab 
4260c0d06caSMauro Carvalho Chehab 	if ((!dmi_check_system(ov9650_flip_dmi_table) &&
427c84e412fSHans de Goede 		sd->vflip->val) ||
4280c0d06caSMauro Carvalho Chehab 		(dmi_check_system(ov9650_flip_dmi_table) &&
429c84e412fSHans de Goede 		!sd->vflip->val))
4300c0d06caSMauro Carvalho Chehab 		ver_offs--;
4310c0d06caSMauro Carvalho Chehab 
4320c0d06caSMauro Carvalho Chehab 	if (width <= 320)
4330c0d06caSMauro Carvalho Chehab 		hor_offs /= 2;
4340c0d06caSMauro Carvalho Chehab 
4350c0d06caSMauro Carvalho Chehab 	/* Synthesize the vsync/hsync setup */
4360c0d06caSMauro Carvalho Chehab 	for (i = 0; i < ARRAY_SIZE(res_init_ov9650) && !err; i++) {
4370c0d06caSMauro Carvalho Chehab 		if (res_init_ov9650[i][0] == BRIDGE)
4380c0d06caSMauro Carvalho Chehab 			err = m5602_write_bridge(sd, res_init_ov9650[i][1],
4390c0d06caSMauro Carvalho Chehab 				res_init_ov9650[i][2]);
4400c0d06caSMauro Carvalho Chehab 		else if (res_init_ov9650[i][0] == SENSOR) {
4410c0d06caSMauro Carvalho Chehab 			data = res_init_ov9650[i][2];
4420c0d06caSMauro Carvalho Chehab 			err = m5602_write_sensor(sd,
4430c0d06caSMauro Carvalho Chehab 				res_init_ov9650[i][1], &data, 1);
4440c0d06caSMauro Carvalho Chehab 		}
4450c0d06caSMauro Carvalho Chehab 	}
4460c0d06caSMauro Carvalho Chehab 	if (err < 0)
4470c0d06caSMauro Carvalho Chehab 		return err;
4480c0d06caSMauro Carvalho Chehab 
4490c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA,
4500c0d06caSMauro Carvalho Chehab 				 ((ver_offs >> 8) & 0xff));
4510c0d06caSMauro Carvalho Chehab 	if (err < 0)
4520c0d06caSMauro Carvalho Chehab 		return err;
4530c0d06caSMauro Carvalho Chehab 
4540c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (ver_offs & 0xff));
4550c0d06caSMauro Carvalho Chehab 	if (err < 0)
4560c0d06caSMauro Carvalho Chehab 		return err;
4570c0d06caSMauro Carvalho Chehab 
4580c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, 0);
4590c0d06caSMauro Carvalho Chehab 	if (err < 0)
4600c0d06caSMauro Carvalho Chehab 		return err;
4610c0d06caSMauro Carvalho Chehab 
4620c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (height >> 8) & 0xff);
4630c0d06caSMauro Carvalho Chehab 	if (err < 0)
4640c0d06caSMauro Carvalho Chehab 		return err;
4650c0d06caSMauro Carvalho Chehab 
4660c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (height & 0xff));
4670c0d06caSMauro Carvalho Chehab 	if (err < 0)
4680c0d06caSMauro Carvalho Chehab 		return err;
4690c0d06caSMauro Carvalho Chehab 
4700c0d06caSMauro Carvalho Chehab 	for (i = 0; i < 2 && !err; i++)
4710c0d06caSMauro Carvalho Chehab 		err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, 0);
4720c0d06caSMauro Carvalho Chehab 	if (err < 0)
4730c0d06caSMauro Carvalho Chehab 		return err;
4740c0d06caSMauro Carvalho Chehab 
4750c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0);
4760c0d06caSMauro Carvalho Chehab 	if (err < 0)
4770c0d06caSMauro Carvalho Chehab 		return err;
4780c0d06caSMauro Carvalho Chehab 
4790c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 2);
4800c0d06caSMauro Carvalho Chehab 	if (err < 0)
4810c0d06caSMauro Carvalho Chehab 		return err;
4820c0d06caSMauro Carvalho Chehab 
4830c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA,
4840c0d06caSMauro Carvalho Chehab 				 (hor_offs >> 8) & 0xff);
4850c0d06caSMauro Carvalho Chehab 	if (err < 0)
4860c0d06caSMauro Carvalho Chehab 		return err;
4870c0d06caSMauro Carvalho Chehab 
4880c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA, hor_offs & 0xff);
4890c0d06caSMauro Carvalho Chehab 	if (err < 0)
4900c0d06caSMauro Carvalho Chehab 		return err;
4910c0d06caSMauro Carvalho Chehab 
4920c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA,
4930c0d06caSMauro Carvalho Chehab 				 ((width + hor_offs) >> 8) & 0xff);
4940c0d06caSMauro Carvalho Chehab 	if (err < 0)
4950c0d06caSMauro Carvalho Chehab 		return err;
4960c0d06caSMauro Carvalho Chehab 
4970c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA,
4980c0d06caSMauro Carvalho Chehab 				 ((width + hor_offs) & 0xff));
4990c0d06caSMauro Carvalho Chehab 	if (err < 0)
5000c0d06caSMauro Carvalho Chehab 		return err;
5010c0d06caSMauro Carvalho Chehab 
5020c0d06caSMauro Carvalho Chehab 	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0);
5030c0d06caSMauro Carvalho Chehab 	if (err < 0)
5040c0d06caSMauro Carvalho Chehab 		return err;
5050c0d06caSMauro Carvalho Chehab 
5060c0d06caSMauro Carvalho Chehab 	switch (width) {
5070c0d06caSMauro Carvalho Chehab 	case 640:
50837d5efb0SJoe Perches 		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for VGA mode\n");
5090c0d06caSMauro Carvalho Chehab 
5100c0d06caSMauro Carvalho Chehab 		data = OV9650_VGA_SELECT | OV9650_RGB_SELECT |
5110c0d06caSMauro Carvalho Chehab 		       OV9650_RAW_RGB_SELECT;
5120c0d06caSMauro Carvalho Chehab 		err = m5602_write_sensor(sd, OV9650_COM7, &data, 1);
5130c0d06caSMauro Carvalho Chehab 		break;
5140c0d06caSMauro Carvalho Chehab 
5150c0d06caSMauro Carvalho Chehab 	case 352:
51637d5efb0SJoe Perches 		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for CIF mode\n");
5170c0d06caSMauro Carvalho Chehab 
5180c0d06caSMauro Carvalho Chehab 		data = OV9650_CIF_SELECT | OV9650_RGB_SELECT |
5190c0d06caSMauro Carvalho Chehab 				OV9650_RAW_RGB_SELECT;
5200c0d06caSMauro Carvalho Chehab 		err = m5602_write_sensor(sd, OV9650_COM7, &data, 1);
5210c0d06caSMauro Carvalho Chehab 		break;
5220c0d06caSMauro Carvalho Chehab 
5230c0d06caSMauro Carvalho Chehab 	case 320:
52437d5efb0SJoe Perches 		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for QVGA mode\n");
5250c0d06caSMauro Carvalho Chehab 
5260c0d06caSMauro Carvalho Chehab 		data = OV9650_QVGA_SELECT | OV9650_RGB_SELECT |
5270c0d06caSMauro Carvalho Chehab 				OV9650_RAW_RGB_SELECT;
5280c0d06caSMauro Carvalho Chehab 		err = m5602_write_sensor(sd, OV9650_COM7, &data, 1);
5290c0d06caSMauro Carvalho Chehab 		break;
5300c0d06caSMauro Carvalho Chehab 
5310c0d06caSMauro Carvalho Chehab 	case 176:
53237d5efb0SJoe Perches 		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for QCIF mode\n");
5330c0d06caSMauro Carvalho Chehab 
5340c0d06caSMauro Carvalho Chehab 		data = OV9650_QCIF_SELECT | OV9650_RGB_SELECT |
5350c0d06caSMauro Carvalho Chehab 			OV9650_RAW_RGB_SELECT;
5360c0d06caSMauro Carvalho Chehab 		err = m5602_write_sensor(sd, OV9650_COM7, &data, 1);
5370c0d06caSMauro Carvalho Chehab 		break;
5380c0d06caSMauro Carvalho Chehab 	}
5390c0d06caSMauro Carvalho Chehab 	return err;
5400c0d06caSMauro Carvalho Chehab }
5410c0d06caSMauro Carvalho Chehab 
5420c0d06caSMauro Carvalho Chehab int ov9650_stop(struct sd *sd)
5430c0d06caSMauro Carvalho Chehab {
5440c0d06caSMauro Carvalho Chehab 	u8 data = OV9650_SOFT_SLEEP | OV9650_OUTPUT_DRIVE_2X;
5450c0d06caSMauro Carvalho Chehab 	return m5602_write_sensor(sd, OV9650_COM2, &data, 1);
5460c0d06caSMauro Carvalho Chehab }
5470c0d06caSMauro Carvalho Chehab 
5480c0d06caSMauro Carvalho Chehab void ov9650_disconnect(struct sd *sd)
5490c0d06caSMauro Carvalho Chehab {
5500c0d06caSMauro Carvalho Chehab 	ov9650_stop(sd);
5510c0d06caSMauro Carvalho Chehab 
5520c0d06caSMauro Carvalho Chehab 	sd->sensor = NULL;
5530c0d06caSMauro Carvalho Chehab }
5540c0d06caSMauro Carvalho Chehab 
5550c0d06caSMauro Carvalho Chehab static int ov9650_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
5560c0d06caSMauro Carvalho Chehab {
5570c0d06caSMauro Carvalho Chehab 	struct sd *sd = (struct sd *) gspca_dev;
5580c0d06caSMauro Carvalho Chehab 	u8 i2c_data;
5590c0d06caSMauro Carvalho Chehab 	int err;
5600c0d06caSMauro Carvalho Chehab 
56137d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_CONF, "Set exposure to %d\n", val);
5620c0d06caSMauro Carvalho Chehab 
5630c0d06caSMauro Carvalho Chehab 	/* The 6 MSBs */
5640c0d06caSMauro Carvalho Chehab 	i2c_data = (val >> 10) & 0x3f;
5650c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_AECHM,
5660c0d06caSMauro Carvalho Chehab 				  &i2c_data, 1);
5670c0d06caSMauro Carvalho Chehab 	if (err < 0)
5680c0d06caSMauro Carvalho Chehab 		return err;
5690c0d06caSMauro Carvalho Chehab 
5700c0d06caSMauro Carvalho Chehab 	/* The 8 middle bits */
5710c0d06caSMauro Carvalho Chehab 	i2c_data = (val >> 2) & 0xff;
5720c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_AECH,
5730c0d06caSMauro Carvalho Chehab 				  &i2c_data, 1);
5740c0d06caSMauro Carvalho Chehab 	if (err < 0)
5750c0d06caSMauro Carvalho Chehab 		return err;
5760c0d06caSMauro Carvalho Chehab 
5770c0d06caSMauro Carvalho Chehab 	/* The 2 LSBs */
5780c0d06caSMauro Carvalho Chehab 	i2c_data = val & 0x03;
5790c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_COM1, &i2c_data, 1);
5800c0d06caSMauro Carvalho Chehab 	return err;
5810c0d06caSMauro Carvalho Chehab }
5820c0d06caSMauro Carvalho Chehab 
5830c0d06caSMauro Carvalho Chehab static int ov9650_set_gain(struct gspca_dev *gspca_dev, __s32 val)
5840c0d06caSMauro Carvalho Chehab {
5850c0d06caSMauro Carvalho Chehab 	int err;
5860c0d06caSMauro Carvalho Chehab 	u8 i2c_data;
5870c0d06caSMauro Carvalho Chehab 	struct sd *sd = (struct sd *) gspca_dev;
5880c0d06caSMauro Carvalho Chehab 
58937d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_CONF, "Setting gain to %d\n", val);
5900c0d06caSMauro Carvalho Chehab 
5910c0d06caSMauro Carvalho Chehab 	/* The 2 MSB */
5920c0d06caSMauro Carvalho Chehab 	/* Read the OV9650_VREF register first to avoid
5930c0d06caSMauro Carvalho Chehab 	   corrupting the VREF high and low bits */
5940c0d06caSMauro Carvalho Chehab 	err = m5602_read_sensor(sd, OV9650_VREF, &i2c_data, 1);
5950c0d06caSMauro Carvalho Chehab 	if (err < 0)
5960c0d06caSMauro Carvalho Chehab 		return err;
5970c0d06caSMauro Carvalho Chehab 
5980c0d06caSMauro Carvalho Chehab 	/* Mask away all uninteresting bits */
5990c0d06caSMauro Carvalho Chehab 	i2c_data = ((val & 0x0300) >> 2) |
6000c0d06caSMauro Carvalho Chehab 			(i2c_data & 0x3f);
6010c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_VREF, &i2c_data, 1);
6020c0d06caSMauro Carvalho Chehab 	if (err < 0)
6030c0d06caSMauro Carvalho Chehab 		return err;
6040c0d06caSMauro Carvalho Chehab 
6050c0d06caSMauro Carvalho Chehab 	/* The 8 LSBs */
6060c0d06caSMauro Carvalho Chehab 	i2c_data = val & 0xff;
6070c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_GAIN, &i2c_data, 1);
6080c0d06caSMauro Carvalho Chehab 	return err;
6090c0d06caSMauro Carvalho Chehab }
6100c0d06caSMauro Carvalho Chehab 
6110c0d06caSMauro Carvalho Chehab static int ov9650_set_red_balance(struct gspca_dev *gspca_dev, __s32 val)
6120c0d06caSMauro Carvalho Chehab {
6130c0d06caSMauro Carvalho Chehab 	int err;
6140c0d06caSMauro Carvalho Chehab 	u8 i2c_data;
6150c0d06caSMauro Carvalho Chehab 	struct sd *sd = (struct sd *) gspca_dev;
6160c0d06caSMauro Carvalho Chehab 
61737d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_CONF, "Set red gain to %d\n", val);
6180c0d06caSMauro Carvalho Chehab 
6190c0d06caSMauro Carvalho Chehab 	i2c_data = val & 0xff;
6200c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_RED, &i2c_data, 1);
6210c0d06caSMauro Carvalho Chehab 	return err;
6220c0d06caSMauro Carvalho Chehab }
6230c0d06caSMauro Carvalho Chehab 
6240c0d06caSMauro Carvalho Chehab static int ov9650_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val)
6250c0d06caSMauro Carvalho Chehab {
6260c0d06caSMauro Carvalho Chehab 	int err;
6270c0d06caSMauro Carvalho Chehab 	u8 i2c_data;
6280c0d06caSMauro Carvalho Chehab 	struct sd *sd = (struct sd *) gspca_dev;
6290c0d06caSMauro Carvalho Chehab 
63037d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_CONF, "Set blue gain to %d\n", val);
6310c0d06caSMauro Carvalho Chehab 
6320c0d06caSMauro Carvalho Chehab 	i2c_data = val & 0xff;
6330c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_BLUE, &i2c_data, 1);
6340c0d06caSMauro Carvalho Chehab 	return err;
6350c0d06caSMauro Carvalho Chehab }
6360c0d06caSMauro Carvalho Chehab 
637c84e412fSHans de Goede static int ov9650_set_hvflip(struct gspca_dev *gspca_dev)
6380c0d06caSMauro Carvalho Chehab {
6390c0d06caSMauro Carvalho Chehab 	int err;
6400c0d06caSMauro Carvalho Chehab 	u8 i2c_data;
6410c0d06caSMauro Carvalho Chehab 	struct sd *sd = (struct sd *) gspca_dev;
642c84e412fSHans de Goede 	int hflip = sd->hflip->val;
643c84e412fSHans de Goede 	int vflip = sd->vflip->val;
6440c0d06caSMauro Carvalho Chehab 
64537d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_CONF, "Set hvflip to %d %d\n", hflip, vflip);
6460c0d06caSMauro Carvalho Chehab 
6470c0d06caSMauro Carvalho Chehab 	if (dmi_check_system(ov9650_flip_dmi_table))
648c84e412fSHans de Goede 		vflip = !vflip;
6490c0d06caSMauro Carvalho Chehab 
650c84e412fSHans de Goede 	i2c_data = (hflip << 5) | (vflip << 4);
6510c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_MVFP, &i2c_data, 1);
6520c0d06caSMauro Carvalho Chehab 	if (err < 0)
6530c0d06caSMauro Carvalho Chehab 		return err;
6540c0d06caSMauro Carvalho Chehab 
6550c0d06caSMauro Carvalho Chehab 	/* When vflip is toggled we need to readjust the bridge hsync/vsync */
6560c0d06caSMauro Carvalho Chehab 	if (gspca_dev->streaming)
6570c0d06caSMauro Carvalho Chehab 		err = ov9650_start(sd);
6580c0d06caSMauro Carvalho Chehab 
6590c0d06caSMauro Carvalho Chehab 	return err;
6600c0d06caSMauro Carvalho Chehab }
6610c0d06caSMauro Carvalho Chehab 
6620c0d06caSMauro Carvalho Chehab static int ov9650_set_auto_exposure(struct gspca_dev *gspca_dev,
6630c0d06caSMauro Carvalho Chehab 				    __s32 val)
6640c0d06caSMauro Carvalho Chehab {
6650c0d06caSMauro Carvalho Chehab 	int err;
6660c0d06caSMauro Carvalho Chehab 	u8 i2c_data;
6670c0d06caSMauro Carvalho Chehab 	struct sd *sd = (struct sd *) gspca_dev;
6680c0d06caSMauro Carvalho Chehab 
66937d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_CONF, "Set auto exposure control to %d\n", val);
6700c0d06caSMauro Carvalho Chehab 
6710c0d06caSMauro Carvalho Chehab 	err = m5602_read_sensor(sd, OV9650_COM8, &i2c_data, 1);
6720c0d06caSMauro Carvalho Chehab 	if (err < 0)
6730c0d06caSMauro Carvalho Chehab 		return err;
6740c0d06caSMauro Carvalho Chehab 
675c84e412fSHans de Goede 	val = (val == V4L2_EXPOSURE_AUTO);
6760c0d06caSMauro Carvalho Chehab 	i2c_data = ((i2c_data & 0xfe) | ((val & 0x01) << 0));
6770c0d06caSMauro Carvalho Chehab 
6780c0d06caSMauro Carvalho Chehab 	return m5602_write_sensor(sd, OV9650_COM8, &i2c_data, 1);
6790c0d06caSMauro Carvalho Chehab }
6800c0d06caSMauro Carvalho Chehab 
6810c0d06caSMauro Carvalho Chehab static int ov9650_set_auto_white_balance(struct gspca_dev *gspca_dev,
6820c0d06caSMauro Carvalho Chehab 					 __s32 val)
6830c0d06caSMauro Carvalho Chehab {
6840c0d06caSMauro Carvalho Chehab 	int err;
6850c0d06caSMauro Carvalho Chehab 	u8 i2c_data;
6860c0d06caSMauro Carvalho Chehab 	struct sd *sd = (struct sd *) gspca_dev;
6870c0d06caSMauro Carvalho Chehab 
68837d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_CONF, "Set auto white balance to %d\n", val);
6890c0d06caSMauro Carvalho Chehab 
6900c0d06caSMauro Carvalho Chehab 	err = m5602_read_sensor(sd, OV9650_COM8, &i2c_data, 1);
6910c0d06caSMauro Carvalho Chehab 	if (err < 0)
6920c0d06caSMauro Carvalho Chehab 		return err;
6930c0d06caSMauro Carvalho Chehab 
6940c0d06caSMauro Carvalho Chehab 	i2c_data = ((i2c_data & 0xfd) | ((val & 0x01) << 1));
6950c0d06caSMauro Carvalho Chehab 	err = m5602_write_sensor(sd, OV9650_COM8, &i2c_data, 1);
6960c0d06caSMauro Carvalho Chehab 
6970c0d06caSMauro Carvalho Chehab 	return err;
6980c0d06caSMauro Carvalho Chehab }
6990c0d06caSMauro Carvalho Chehab 
7000c0d06caSMauro Carvalho Chehab static int ov9650_set_auto_gain(struct gspca_dev *gspca_dev, __s32 val)
7010c0d06caSMauro Carvalho Chehab {
7020c0d06caSMauro Carvalho Chehab 	int err;
7030c0d06caSMauro Carvalho Chehab 	u8 i2c_data;
7040c0d06caSMauro Carvalho Chehab 	struct sd *sd = (struct sd *) gspca_dev;
7050c0d06caSMauro Carvalho Chehab 
70637d5efb0SJoe Perches 	gspca_dbg(gspca_dev, D_CONF, "Set auto gain control to %d\n", val);
7070c0d06caSMauro Carvalho Chehab 
7080c0d06caSMauro Carvalho Chehab 	err = m5602_read_sensor(sd, OV9650_COM8, &i2c_data, 1);
7090c0d06caSMauro Carvalho Chehab 	if (err < 0)
7100c0d06caSMauro Carvalho Chehab 		return err;
7110c0d06caSMauro Carvalho Chehab 
7120c0d06caSMauro Carvalho Chehab 	i2c_data = ((i2c_data & 0xfb) | ((val & 0x01) << 2));
7130c0d06caSMauro Carvalho Chehab 
7140c0d06caSMauro Carvalho Chehab 	return m5602_write_sensor(sd, OV9650_COM8, &i2c_data, 1);
7150c0d06caSMauro Carvalho Chehab }
7160c0d06caSMauro Carvalho Chehab 
717c84e412fSHans de Goede static int ov9650_s_ctrl(struct v4l2_ctrl *ctrl)
718c84e412fSHans de Goede {
719c84e412fSHans de Goede 	struct gspca_dev *gspca_dev =
720c84e412fSHans de Goede 		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
721c84e412fSHans de Goede 	struct sd *sd = (struct sd *) gspca_dev;
722c84e412fSHans de Goede 	int err;
723c84e412fSHans de Goede 
724c84e412fSHans de Goede 	if (!gspca_dev->streaming)
725c84e412fSHans de Goede 		return 0;
726c84e412fSHans de Goede 
727c84e412fSHans de Goede 	switch (ctrl->id) {
728c84e412fSHans de Goede 	case V4L2_CID_AUTO_WHITE_BALANCE:
729c84e412fSHans de Goede 		err = ov9650_set_auto_white_balance(gspca_dev, ctrl->val);
730c84e412fSHans de Goede 		if (err || ctrl->val)
731c84e412fSHans de Goede 			return err;
732c84e412fSHans de Goede 		err = ov9650_set_red_balance(gspca_dev, sd->red_bal->val);
733c84e412fSHans de Goede 		if (err)
734c84e412fSHans de Goede 			return err;
735c84e412fSHans de Goede 		err = ov9650_set_blue_balance(gspca_dev, sd->blue_bal->val);
736c84e412fSHans de Goede 		break;
737c84e412fSHans de Goede 	case V4L2_CID_EXPOSURE_AUTO:
738c84e412fSHans de Goede 		err = ov9650_set_auto_exposure(gspca_dev, ctrl->val);
739c84e412fSHans de Goede 		if (err || ctrl->val == V4L2_EXPOSURE_AUTO)
740c84e412fSHans de Goede 			return err;
741c84e412fSHans de Goede 		err = ov9650_set_exposure(gspca_dev, sd->expo->val);
742c84e412fSHans de Goede 		break;
743c84e412fSHans de Goede 	case V4L2_CID_AUTOGAIN:
744c84e412fSHans de Goede 		err = ov9650_set_auto_gain(gspca_dev, ctrl->val);
745c84e412fSHans de Goede 		if (err || ctrl->val)
746c84e412fSHans de Goede 			return err;
747c84e412fSHans de Goede 		err = ov9650_set_gain(gspca_dev, sd->gain->val);
748c84e412fSHans de Goede 		break;
749c84e412fSHans de Goede 	case V4L2_CID_HFLIP:
750c84e412fSHans de Goede 		err = ov9650_set_hvflip(gspca_dev);
751c84e412fSHans de Goede 		break;
752c84e412fSHans de Goede 	default:
753c84e412fSHans de Goede 		return -EINVAL;
754c84e412fSHans de Goede 	}
755c84e412fSHans de Goede 
756c84e412fSHans de Goede 	return err;
757c84e412fSHans de Goede }
758c84e412fSHans de Goede 
7590c0d06caSMauro Carvalho Chehab static void ov9650_dump_registers(struct sd *sd)
7600c0d06caSMauro Carvalho Chehab {
7610c0d06caSMauro Carvalho Chehab 	int address;
7620c0d06caSMauro Carvalho Chehab 	pr_info("Dumping the ov9650 register state\n");
7630c0d06caSMauro Carvalho Chehab 	for (address = 0; address < 0xa9; address++) {
7640c0d06caSMauro Carvalho Chehab 		u8 value;
7650c0d06caSMauro Carvalho Chehab 		m5602_read_sensor(sd, address, &value, 1);
7660c0d06caSMauro Carvalho Chehab 		pr_info("register 0x%x contains 0x%x\n", address, value);
7670c0d06caSMauro Carvalho Chehab 	}
7680c0d06caSMauro Carvalho Chehab 
7690c0d06caSMauro Carvalho Chehab 	pr_info("ov9650 register state dump complete\n");
7700c0d06caSMauro Carvalho Chehab 
7710c0d06caSMauro Carvalho Chehab 	pr_info("Probing for which registers that are read/write\n");
7720c0d06caSMauro Carvalho Chehab 	for (address = 0; address < 0xff; address++) {
7730c0d06caSMauro Carvalho Chehab 		u8 old_value, ctrl_value;
7740c0d06caSMauro Carvalho Chehab 		u8 test_value[2] = {0xff, 0xff};
7750c0d06caSMauro Carvalho Chehab 
7760c0d06caSMauro Carvalho Chehab 		m5602_read_sensor(sd, address, &old_value, 1);
7770c0d06caSMauro Carvalho Chehab 		m5602_write_sensor(sd, address, test_value, 1);
7780c0d06caSMauro Carvalho Chehab 		m5602_read_sensor(sd, address, &ctrl_value, 1);
7790c0d06caSMauro Carvalho Chehab 
7800c0d06caSMauro Carvalho Chehab 		if (ctrl_value == test_value[0])
7810c0d06caSMauro Carvalho Chehab 			pr_info("register 0x%x is writeable\n", address);
7820c0d06caSMauro Carvalho Chehab 		else
7830c0d06caSMauro Carvalho Chehab 			pr_info("register 0x%x is read only\n", address);
7840c0d06caSMauro Carvalho Chehab 
7850c0d06caSMauro Carvalho Chehab 		/* Restore original value */
7860c0d06caSMauro Carvalho Chehab 		m5602_write_sensor(sd, address, &old_value, 1);
7870c0d06caSMauro Carvalho Chehab 	}
7880c0d06caSMauro Carvalho Chehab }
789