Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dcef3237 authored by Hans de Goede's avatar Hans de Goede Committed by Mauro Carvalho Chehab
Browse files

V4L/DVB (8348): gspca: Add auto gain/exposure to sonixb and tas5110 / ov6650 sensors.



sonixb:   Do auto gain for tas5110 / ov6650 sensors.
pac207:   Move the auto_gain function to gspca.
gspca:    New function gspca_auto_gain_n_exposure().

Signed-off-by: default avatarHans de Goede <j.w.r.degoede@hhs.nl>
Signed-off-by: default avatarJean-Francois Moine <moinejf@free.fr>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@infradead.org>
parent d0d0e39b
Loading
Loading
Loading
Loading
+88 −0
Original line number Diff line number Diff line
@@ -1790,6 +1790,94 @@ void gspca_disconnect(struct usb_interface *intf)
}
EXPORT_SYMBOL(gspca_disconnect);

/* -- cam driver utility functions -- */

/* auto gain and exposure algorithm based on the knee algorithm described here:
   http://ytse.tricolour.net/docs/LowLightOptimization.html

   Returns 0 if no changes were made, 1 if the gain and or exposure settings
   where changed. */
int gspca_auto_gain_n_exposure(struct gspca_dev *gspca_dev, int avg_lum,
	int desired_avg_lum, int deadzone, int gain_knee, int exposure_knee)
{
	int i, steps, gain, orig_gain, exposure, orig_exposure, autogain;
	const struct ctrl *gain_ctrl = NULL;
	const struct ctrl *exposure_ctrl = NULL;
	const struct ctrl *autogain_ctrl = NULL;
	int retval = 0;

	for (i = 0; i < gspca_dev->sd_desc->nctrls; i++) {
		if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_GAIN)
			gain_ctrl = &gspca_dev->sd_desc->ctrls[i];
		if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_EXPOSURE)
			exposure_ctrl = &gspca_dev->sd_desc->ctrls[i];
		if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_AUTOGAIN)
			autogain_ctrl = &gspca_dev->sd_desc->ctrls[i];
	}
	if (!gain_ctrl || !exposure_ctrl || !autogain_ctrl) {
		PDEBUG(D_ERR, "Error: gspca_auto_gain_n_exposure called "
			"on cam without (auto)gain/exposure");
		return 0;
	}

	if (gain_ctrl->get(gspca_dev, &gain) ||
			exposure_ctrl->get(gspca_dev, &exposure) ||
			autogain_ctrl->get(gspca_dev, &autogain) || !autogain)
		return 0;

	orig_gain = gain;
	orig_exposure = exposure;

	/* If we are of a multiple of deadzone, do multiple steps to reach the
	   desired lumination fast (with the risc of a slight overshoot) */
	steps = abs(desired_avg_lum - avg_lum) / deadzone;

	PDEBUG(D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n",
		avg_lum, desired_avg_lum, steps);

	for (i = 0; i < steps; i++) {
		if (avg_lum > desired_avg_lum) {
			if (gain > gain_knee)
				gain--;
			else if (exposure > exposure_knee)
				exposure--;
			else if (gain > gain_ctrl->qctrl.default_value)
				gain--;
			else if (exposure > exposure_ctrl->qctrl.minimum)
				exposure--;
			else if (gain > gain_ctrl->qctrl.minimum)
				gain--;
			else
				break;
		} else {
			if (gain < gain_ctrl->qctrl.default_value)
				gain++;
			else if (exposure < exposure_knee)
				exposure++;
			else if (gain < gain_knee)
				gain++;
			else if (exposure < exposure_ctrl->qctrl.maximum)
				exposure++;
			else if (gain < gain_ctrl->qctrl.maximum)
				gain++;
			else
				break;
		}
	}

	if (gain != orig_gain) {
		gain_ctrl->set(gspca_dev, gain);
		retval = 1;
	}
	if (exposure != orig_exposure) {
		exposure_ctrl->set(gspca_dev, exposure);
		retval = 1;
	}

	return retval;
}
EXPORT_SYMBOL(gspca_auto_gain_n_exposure);

/* -- module insert / remove -- */
static int __init gspca_init(void)
{
+2 −0
Original line number Diff line number Diff line
@@ -170,4 +170,6 @@ struct gspca_frame *gspca_frame_add(struct gspca_dev *gspca_dev,
				    struct gspca_frame *frame,
				    const __u8 *data,
				    int len);
int gspca_auto_gain_n_exposure(struct gspca_dev *gspca_dev, int avg_lum,
	int desired_avg_lum, int deadzone, int gain_knee, int exposure_knee);
#endif /* GSPCAV2_H */
+5 −63
Original line number Diff line number Diff line
@@ -357,71 +357,21 @@ static void sd_close(struct gspca_dev *gspca_dev)
{
}

/* auto gain and exposure algorithm based on the knee algorithm described here:
 * <http://ytse.tricolour.net/docs/LowLightOptimization.html> */
static void pac207_do_auto_gain(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	int i, steps, desired_avg_lum;
	int orig_gain = sd->gain;
	int orig_exposure = sd->exposure;
	int avg_lum = atomic_read(&sd->avg_lum);

	if (!sd->autogain || avg_lum == -1)
	if (avg_lum == -1)
		return;

	if (sd->autogain_ignore_frames > 0) {
	if (sd->autogain_ignore_frames > 0)
		sd->autogain_ignore_frames--;
		return;
	}

	/* correct desired lumination for the configured brightness */
	desired_avg_lum = 100 + sd->brightness / 2;

	/* If we are of a multiple of deadzone, do multiple step to reach the
	   desired lumination fast (with the risc of a slight overshoot) */
	steps = abs(desired_avg_lum - avg_lum) / PAC207_AUTOGAIN_DEADZONE;

	for (i = 0; i < steps; i++) {
		if (avg_lum > desired_avg_lum) {
			if (sd->gain > PAC207_GAIN_KNEE)
				sd->gain--;
			else if (sd->exposure > PAC207_EXPOSURE_KNEE)
				sd->exposure--;
			else if (sd->gain > PAC207_GAIN_DEFAULT)
				sd->gain--;
			else if (sd->exposure > PAC207_EXPOSURE_MIN)
				sd->exposure--;
			else if (sd->gain > PAC207_GAIN_MIN)
				sd->gain--;
			else
				break;
		} else {
			if (sd->gain < PAC207_GAIN_DEFAULT)
				sd->gain++;
			else if (sd->exposure < PAC207_EXPOSURE_KNEE)
				sd->exposure++;
			else if (sd->gain < PAC207_GAIN_KNEE)
				sd->gain++;
			else if (sd->exposure < PAC207_EXPOSURE_MAX)
				sd->exposure++;
			else if (sd->gain < PAC207_GAIN_MAX)
				sd->gain++;
			else
				break;
		}
	}

	if (sd->exposure != orig_exposure || sd->gain != orig_gain) {
		if (sd->exposure != orig_exposure)
			pac207_write_reg(gspca_dev, 0x0002, sd->exposure);
		if (sd->gain != orig_gain)
			pac207_write_reg(gspca_dev, 0x000e, sd->gain);
		pac207_write_reg(gspca_dev, 0x13, 0x01); /* load reg to sen */
		pac207_write_reg(gspca_dev, 0x1c, 0x01); /* not documented */
	else if (gspca_auto_gain_n_exposure(gspca_dev, avg_lum,
			100 + sd->brightness / 2, PAC207_AUTOGAIN_DEADZONE,
			PAC207_GAIN_KNEE, PAC207_EXPOSURE_KNEE))
		sd->autogain_ignore_frames = PAC207_AUTOGAIN_IGNORE_FRAMES;
}
}

static unsigned char *pac207_find_sof(struct gspca_dev *gspca_dev,
					unsigned char *m, int len)
@@ -546,10 +496,6 @@ static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	/* don't allow mucking with exposure when using autogain */
	if (sd->autogain)
		return -EINVAL;

	sd->exposure = val;
	if (gspca_dev->streaming)
		setexposure(gspca_dev);
@@ -568,10 +514,6 @@ static int sd_setgain(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	/* don't allow mucking with gain when using autogain */
	if (sd->autogain)
		return -EINVAL;

	sd->gain = val;
	if (gspca_dev->streaming)
		setgain(gspca_dev);
+255 −46
Original line number Diff line number Diff line
@@ -35,11 +35,19 @@ MODULE_LICENSE("GPL");
struct sd {
	struct gspca_dev gspca_dev;	/* !! must be the first item */

	struct sd_desc sd_desc;		/* our nctrls differ dependend upon the
					   sensor, so we use a per cam copy */
	atomic_t avg_lum;

	unsigned short gain;
	unsigned short exposure;
	unsigned char brightness;
	unsigned char contrast;
	unsigned char autogain;
	unsigned char autogain_ignore_frames;

	unsigned char fr_h_sz;		/* size of frame header */
	char sensor;			/* Type of image sensor chip */
	char sensor_has_gain;
#define SENSOR_HV7131R 0
#define SENSOR_OV6650 1
#define SENSOR_OV7630 2
@@ -59,11 +67,24 @@ struct sd {

#define SYS_CLK 0x04

/* We calculate the autogain at the end of the transfer of a frame, at this
   moment a frame with the old settings is being transmitted, and a frame is
   being captured with the old settings. So if we adjust the autogain we must
   ignore atleast the 2 next frames for the new settings to come into effect
   before doing any other adjustments */
#define AUTOGAIN_IGNORE_FRAMES 3
#define AUTOGAIN_DEADZONE 500
#define DESIRED_AVG_LUM 7000

/* V4L2 controls supported by the driver */
static int sd_setbrightness(struct gspca_dev *gspca_dev, __s32 val);
static int sd_getbrightness(struct gspca_dev *gspca_dev, __s32 *val);
static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val);
static int sd_getcontrast(struct gspca_dev *gspca_dev, __s32 *val);
static int sd_setgain(struct gspca_dev *gspca_dev, __s32 val);
static int sd_getgain(struct gspca_dev *gspca_dev, __s32 *val);
static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val);
static int sd_getexposure(struct gspca_dev *gspca_dev, __s32 *val);
static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val);
static int sd_getautogain(struct gspca_dev *gspca_dev, __s32 *val);

static struct ctrl sd_ctrls[] = {
#define SD_BRIGHTNESS 0
@@ -75,24 +96,59 @@ static struct ctrl sd_ctrls[] = {
		.minimum = 0,
		.maximum = 255,
		.step    = 1,
		.default_value = 127,
#define BRIGHTNESS_DEF 127
		.default_value = BRIGHTNESS_DEF,
	    },
	    .set = sd_setbrightness,
	    .get = sd_getbrightness,
	},
#define SD_CONTRAST 1
#define SD_GAIN 1
	{
	    {
		.id      = V4L2_CID_CONTRAST,
		.id      = V4L2_CID_GAIN,
		.type    = V4L2_CTRL_TYPE_INTEGER,
		.name    = "Contrast",
		.name    = "Gain",
		.minimum = 0,
		.maximum = 255,
		.maximum = 511,
		.step    = 1,
		.default_value = 127,
#define GAIN_DEF 255
#define GAIN_KNEE 400
		.default_value = GAIN_DEF,
	    },
	    .set = sd_setcontrast,
	    .get = sd_getcontrast,
	    .set = sd_setgain,
	    .get = sd_getgain,
	},
#define SD_EXPOSURE 2
	{
		{
			.id = V4L2_CID_EXPOSURE,
			.type = V4L2_CTRL_TYPE_INTEGER,
			.name = "Exposure",
#define EXPOSURE_DEF 0
#define EXPOSURE_KNEE 353 /* 10 fps */
			.minimum = 0,
			.maximum = 511,
			.step = 1,
			.default_value = EXPOSURE_DEF,
			.flags = 0,
		},
		.set = sd_setexposure,
		.get = sd_getexposure,
	},
#define SD_AUTOGAIN 3
	{
		{
			.id = V4L2_CID_AUTOGAIN,
			.type = V4L2_CTRL_TYPE_BOOLEAN,
			.name = "Automatic Gain (and Exposure)",
			.minimum = 0,
			.maximum = 1,
			.step = 1,
			.default_value = 1,
			.flags = 0,
		},
		.set = sd_setautogain,
		.get = sd_getautogain,
	},
};

@@ -153,8 +209,12 @@ static const __u8 ov6650_sensor_init[][8] =
	/* Bright, contrast, etc are set througth SCBB interface.
	 * AVCAP on win2 do not send any data on this 	controls. */
	/* Anyway, some registers appears to alter bright and constrat */

	/* Reset sensor */
	{0xa0, 0x60, 0x12, 0x80, 0x00, 0x00, 0x00, 0x10},
	/* Set clock register 0x11 low nibble is clock divider */
	{0xd0, 0x60, 0x11, 0xc0, 0x1b, 0x18, 0xc1, 0x10},
	/* Next some unknown stuff */
	{0xb0, 0x60, 0x15, 0x00, 0x02, 0x18, 0xc1, 0x10},
/*	{0xa0, 0x60, 0x1b, 0x01, 0x02, 0x18, 0xc1, 0x10},
		 * THIS SET GREEN SCREEN
@@ -163,31 +223,18 @@ static const __u8 ov6650_sensor_init[][8] =
	{0xd0, 0x60, 0x26, 0x01, 0x14, 0xd8, 0xa4, 0x10}, /* format out? */
	{0xd0, 0x60, 0x26, 0x01, 0x14, 0xd8, 0xa4, 0x10},
	{0xa0, 0x60, 0x30, 0x3d, 0x0A, 0xd8, 0xa4, 0x10},
	/* Disable autobright ? */
	{0xb0, 0x60, 0x60, 0x66, 0x68, 0xd8, 0xa4, 0x10},
	/* Some more unknown stuff */
	{0xa0, 0x60, 0x68, 0x04, 0x68, 0xd8, 0xa4, 0x10},
	{0xd0, 0x60, 0x17, 0x24, 0xd6, 0x04, 0x94, 0x10}, /* Clipreg */
	{0xa0, 0x60, 0x10, 0x5d, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x2d, 0x0a, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x32, 0x00, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x33, 0x40, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x11, 0xc0, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x00, 0x16, 0x99, 0x04, 0x94, 0x15}, /* bright / Lumino */
	{0xa0, 0x60, 0x2b, 0xab, 0x99, 0x04, 0x94, 0x15},
							/* ?flicker o brillo */
	{0xa0, 0x60, 0x2d, 0x2a, 0x99, 0x04, 0x94, 0x15},
	{0xa0, 0x60, 0x2d, 0x2b, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x32, 0x00, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x33, 0x00, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x10, 0x57, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x2d, 0x2b, 0x99, 0x04, 0x94, 0x16},
	{0xa0, 0x60, 0x32, 0x00, 0x99, 0x04, 0x94, 0x16},
		/* Low Light (Enabled: 0x32 0x1 | Disabled: 0x32 0x00) */
	{0xa0, 0x60, 0x33, 0x29, 0x99, 0x04, 0x94, 0x16},
		/* Low Ligth (Enabled: 0x33 0x13 | Disabled: 0x33 0x29) */
/*	{0xa0, 0x60, 0x11, 0xc1, 0x99, 0x04, 0x94, 0x16}, */
	{0xa0, 0x60, 0x00, 0x17, 0x99, 0x04, 0x94, 0x15}, /* clip? r */
	{0xa0, 0x60, 0x00, 0x18, 0x99, 0x04, 0x94, 0x15}, /* clip? r */
	/* Framerate adjust register for artificial light 50 hz flicker
	   compensation, identical to ov6630 0x2b register, see 6630 datasheet.
	   0x4f -> (30 fps -> 25 fps), 0x00 -> no adjustment */
	{0xa0, 0x60, 0x2b, 0x4f, 0x99, 0x04, 0x94, 0x15},
};

static const __u8 initOv7630[] = {
	0x04, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,	/* r01 .. r08 */
	0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* r09 .. r10 */
@@ -469,8 +516,7 @@ static void setbrightness(struct gspca_dev *gspca_dev)
			goto err;
		break;
	    }
	case SENSOR_TAS5130CXX:
	case SENSOR_TAS5110: {
	case SENSOR_TAS5130CXX: {
		__u8 i2c[] =
			{0x30, 0x11, 0x02, 0x20, 0x70, 0x00, 0x00, 0x10};

@@ -481,24 +527,112 @@ static void setbrightness(struct gspca_dev *gspca_dev)
			goto err;
		break;
	    }
	case SENSOR_TAS5110:
		/* FIXME figure out howto control brightness on TAS5110 */
		break;
	}
	return;
err:
	PDEBUG(D_ERR, "i2c error brightness");
}
static void setcontrast(struct gspca_dev *gspca_dev)

static void setsensorgain(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	unsigned short gain;

	gain = (sd->gain + 1) >> 1;
	if (gain > 255)
		gain = 255;

	switch (sd->sensor) {

	case SENSOR_TAS5110: {
		__u8 i2c[] =
			{0x30, 0x11, 0x02, 0x20, 0x70, 0x00, 0x00, 0x10};

		i2c[4] = 255 - gain;
		if (i2c_w(gspca_dev->dev, i2c) < 0)
			goto err;
		break; }

	case SENSOR_OV6650: {
		__u8 i2c[] = {0xa0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10};
		i2c[3] = gain;
		if (i2c_w(gspca_dev->dev, i2c) < 0)
			goto err;
		break; }
	}
	return;
err:
	PDEBUG(D_ERR, "i2c error gain");
}

static void setgain(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	__u8 gain;
	__u8 rgb_value;

	gain = sd->contrast >> 4;
	gain = sd->gain >> 5;

	/* red and blue gain */
	rgb_value = gain << 4 | gain;
	reg_w(gspca_dev->dev, 0x10, &rgb_value, 1);
	/* green gain */
	rgb_value = gain;
	reg_w(gspca_dev->dev, 0x11, &rgb_value, 1);

	if (sd->sensor_has_gain)
		setsensorgain(gspca_dev);
}

static void setexposure(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	/* translate 0 - 255 to a number of fps in a 30 - 1 scale */
	int fps = 30 - sd->exposure * 29 / 511;

	switch (sd->sensor) {
	case SENSOR_TAS5110: {
		__u8 reg;

		/* register 19's high nibble contains the sn9c10x clock divider
		   The high nibble configures the no fps according to the
		   formula: 60 / high_nibble. With a maximum of 30 fps */
		reg = 60 / fps;
		if (reg > 15)
			reg = 15;
		reg = (reg << 4) | 0x0b;
		reg_w(gspca_dev->dev, 0x19, &reg, 1);
		break; }
	case SENSOR_OV6650: {
		__u8 i2c[] = {0xa0, 0x60, 0x11, 0xc0, 0x00, 0x00, 0x00, 0x10};
		i2c[3] = 30 / fps - 1;
		if (i2c[3] > 15)
			i2c[3] = 15;
		i2c[3] |= 0xc0;
		if (i2c_w(gspca_dev->dev, i2c) < 0)
			PDEBUG(D_ERR, "i2c error exposure");
		break; }
	}
}


static void do_autogain(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	int avg_lum = atomic_read(&sd->avg_lum);

	if (avg_lum == -1)
		return;

	if (sd->autogain_ignore_frames > 0)
		sd->autogain_ignore_frames--;
	else if (gspca_auto_gain_n_exposure(gspca_dev, avg_lum,
			sd->brightness * DESIRED_AVG_LUM / 127,
			AUTOGAIN_DEADZONE, GAIN_KNEE, EXPOSURE_KNEE))
		sd->autogain_ignore_frames = AUTOGAIN_IGNORE_FRAMES;
}

/* this function is called at probe time */
@@ -511,7 +645,13 @@ static int sd_config(struct gspca_dev *gspca_dev,
	__u16 product;
	int sif = 0;

	/* nctrls depends upon the sensor, so we use a per cam copy */
	memcpy(&sd->sd_desc, gspca_dev->sd_desc, sizeof(struct sd_desc));
	gspca_dev->sd_desc = &sd->sd_desc;

	sd->fr_h_sz = 12;		/* default size of the frame header */
	sd->sd_desc.nctrls = 2;		/* default no ctrls */

/*	vendor = id->idVendor; */
	product = id->idProduct;
/*	switch (vendor) { */
@@ -521,6 +661,9 @@ static int sd_config(struct gspca_dev *gspca_dev,
		case 0x6005:			/* SN9C101 */
		case 0x6007:			/* SN9C101 */
			sd->sensor = SENSOR_TAS5110;
			sd->sensor_has_gain = 1;
			sd->sd_desc.nctrls = 4;
			sd->sd_desc.dq_callback = do_autogain;
			sif = 1;
			break;
		case 0x6009:			/* SN9C101 */
@@ -531,6 +674,9 @@ static int sd_config(struct gspca_dev *gspca_dev,
			break;
		case 0x6011:			/* SN9C101 - SN9C101G */
			sd->sensor = SENSOR_OV6650;
			sd->sensor_has_gain = 1;
			sd->sd_desc.nctrls = 4;
			sd->sd_desc.dq_callback = do_autogain;
			sif = 1;
			break;
		case 0x6019:			/* SN9C101 */
@@ -570,8 +716,10 @@ static int sd_config(struct gspca_dev *gspca_dev,
		cam->cam_mode = sif_mode;
		cam->nmodes = sizeof sif_mode / sizeof sif_mode[0];
	}
	sd->brightness = sd_ctrls[SD_BRIGHTNESS].qctrl.default_value;
	sd->contrast = sd_ctrls[SD_CONTRAST].qctrl.default_value;
	sd->brightness = BRIGHTNESS_DEF;
	sd->gain = GAIN_DEF;
	sd->exposure = EXPOSURE_DEF;
	sd->autogain = 1;
	if (sd->sensor == SENSOR_OV7630_3)	/* jfm: from win trace */
		reg_w(gspca_dev->dev, 0x01, probe_ov7630, sizeof probe_ov7630);
	return 0;
@@ -754,8 +902,12 @@ static void sd_start(struct gspca_dev *gspca_dev)
	reg_w(dev, 0x18, &reg17_19[1], 2);
	msleep(20);

	setcontrast(gspca_dev);
	setgain(gspca_dev);
	setbrightness(gspca_dev);
	setexposure(gspca_dev);

	sd->autogain_ignore_frames = 0;
	atomic_set(&sd->avg_lum, -1);
}

static void sd_stopN(struct gspca_dev *gspca_dev)
@@ -779,8 +931,8 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev,
			unsigned char *data,		/* isoc packet */
			int len)			/* iso packet length */
{
	struct sd *sd;
	int i;
	struct sd *sd = (struct sd *) gspca_dev;

	if (len > 6 && len < 24) {
		for (i = 0; i < len - 6; i++) {
@@ -792,7 +944,16 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev,
			    && data[5 + i] == 0x96) {	/* start of frame */
				frame = gspca_frame_add(gspca_dev, LAST_PACKET,
							frame, data, 0);
				sd = (struct sd *) gspca_dev;
				if (i < (len - 10)) {
					atomic_set(&sd->avg_lum, data[i + 8] +
							(data[i + 9] << 8));
				} else {
					atomic_set(&sd->avg_lum, -1);
#ifdef CONFIG_VIDEO_ADV_DEBUG
					PDEBUG(D_STREAM, "packet too short to "
						"get avg brightness");
#endif
				}
				data += i + sd->fr_h_sz;
				len -= i + sd->fr_h_sz;
				gspca_frame_add(gspca_dev, FIRST_PACKET,
@@ -823,26 +984,74 @@ static int sd_getbrightness(struct gspca_dev *gspca_dev, __s32 *val)
	return 0;
}

static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val)
static int sd_setgain(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	sd->contrast = val;
	sd->gain = val;
	if (gspca_dev->streaming)
		setcontrast(gspca_dev);
		setgain(gspca_dev);
	return 0;
}

static int sd_getgain(struct gspca_dev *gspca_dev, __s32 *val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	*val = sd->gain;
	return 0;
}

static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	sd->exposure = val;
	if (gspca_dev->streaming)
		setexposure(gspca_dev);
	return 0;
}

static int sd_getexposure(struct gspca_dev *gspca_dev, __s32 *val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	*val = sd->exposure;
	return 0;
}

static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	sd->autogain = val;
	/* when switching to autogain set defaults to make sure
	   we are on a valid point of the autogain gain /
	   exposure knee graph, and give this change time to
	   take effect before doing autogain. */
	if (sd->autogain) {
		sd->exposure = EXPOSURE_DEF;
		sd->gain = GAIN_DEF;
		if (gspca_dev->streaming) {
			sd->autogain_ignore_frames = AUTOGAIN_IGNORE_FRAMES;
			setexposure(gspca_dev);
			setgain(gspca_dev);
		}
	}

	return 0;
}

static int sd_getcontrast(struct gspca_dev *gspca_dev, __s32 *val)
static int sd_getautogain(struct gspca_dev *gspca_dev, __s32 *val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	*val = sd->contrast;
	*val = sd->autogain;
	return 0;
}

/* sub-driver description */
static struct sd_desc sd_desc = {
static const struct sd_desc sd_desc = {
	.name = MODULE_NAME,
	.ctrls = sd_ctrls,
	.nctrls = ARRAY_SIZE(sd_ctrls),