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

Commit 7879d459 authored by Jean-Francois Moine's avatar Jean-Francois Moine Committed by Mauro Carvalho Chehab
Browse files

V4L/DVB (8672): gspca: Big rewrite of spca561.



Bug register/value inversions in USB exchanges.
Exposure and gain controls added for rev 12a.
Separate the functions and controls of the revisions 12a and 72a.

Signed-off-by: default avatarJean-Francois Moine <moinejf@free.fr>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@infradead.org>
parent a0b508c2
Loading
Loading
Loading
Loading
+336 −260
Original line number Diff line number Diff line
@@ -32,85 +32,44 @@ MODULE_LICENSE("GPL");
struct sd {
	struct gspca_dev gspca_dev;	/* !! must be the first item */

	unsigned short contrast;
	__u8 brightness;
	__u8 white;
	__u16 contrast;			/* rev72a only */
#define CONTRAST_MIN 0x0000
#define CONTRAST_DEF 0x2000
#define CONTRAST_MAX 0x3fff

	__u16 exposure;			/* rev12a only */
#define EXPOSURE_MIN 0x0120
#define EXPOSURE_DEF 0x20ae
#define EXPOSURE_MAX 0x5720

	__u8 brightness;		/* rev72a only */
#define BRIGHTNESS_MIN 0
#define BRIGHTNESS_DEF 32
#define BRIGHTNESS_MAX 63

	__u8 white;			/* rev12a only */
#define WHITE_MIN 0
#define WHITE_DEF 0x40
#define WHITE_MAX 0x7f

	__u8 autogain;
#define AUTOGAIN_MIN 0
#define AUTOGAIN_DEF 1
#define AUTOGAIN_MAX 1

	__u8 gain;			/* rev12a only */
#define GAIN_MIN 0x0
#define GAIN_DEF 0x24
#define GAIN_MAX 0x24

	__u8 chip_revision;
#define Rev012A 0
#define Rev072A 1

	signed char ag_cnt;
#define AG_CNT_START 13
};

/* 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_setwhite(struct gspca_dev *gspca_dev, __s32 val);
static int sd_getwhite(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[] = {
	{
	 {
	  .id = V4L2_CID_BRIGHTNESS,
	  .type = V4L2_CTRL_TYPE_INTEGER,
	  .name = "Brightness",
	  .minimum = 0,
	  .maximum = 63,
	  .step = 1,
#define BRIGHTNESS_DEF 32
		.default_value = BRIGHTNESS_DEF,
	  },
	 .set = sd_setbrightness,
	 .get = sd_getbrightness,
	 },
	{
	 {
	  .id = V4L2_CID_CONTRAST,
	  .type = V4L2_CTRL_TYPE_INTEGER,
	  .name = "Contrast",
	  .minimum = 0,
	  .maximum = 0x3fff,
	  .step = 1,
#define CONTRAST_DEF 0x2000
		.default_value = CONTRAST_DEF,
	  },
	 .set = sd_setcontrast,
	 .get = sd_getcontrast,
	 },
	{
	    {
		.id = V4L2_CID_DO_WHITE_BALANCE,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "While Balance",
		.minimum = 0,
		.maximum = 0x7f,
		.step = 1,
#define WHITE_DEF 40
		.default_value = WHITE_DEF,
		},
		.set = sd_setwhite,
		.get = sd_getwhite,
	    },
	{
	 {
	  .id = V4L2_CID_AUTOGAIN,
	  .type = V4L2_CTRL_TYPE_BOOLEAN,
	  .name = "Auto Gain",
	  .minimum = 0,
	  .maximum = 1,
	  .step = 1,
#define AUTOGAIN_DEF 1
	  .default_value = AUTOGAIN_DEF,
	  },
	 .set = sd_setautogain,
	 .get = sd_getautogain,
	 },
};

static struct v4l2_pix_format sif_mode[] = {
	{160, 120, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
		.bytesperline = 160,
@@ -160,12 +119,8 @@ static struct v4l2_pix_format sif_mode[] = {
#define SPCA561_INDEX_I2C_BASE 0x8800
#define SPCA561_SNAPBIT 0x20
#define SPCA561_SNAPCTRL 0x40
enum {
	Rev072A = 0,
	Rev012A,
};

static void reg_w_val(struct usb_device *dev, __u16 index, __u16 value)
static void reg_w_val(struct usb_device *dev, __u16 index, __u8 value)
{
	int ret;

@@ -215,12 +170,6 @@ static void reg_w_buf(struct gspca_dev *gspca_dev,
			index, gspca_dev->usb_buf, len, 500);
}

static void i2c_init(struct gspca_dev *gspca_dev, __u8 mode)
{
	reg_w_val(gspca_dev->dev, 0x92, 0x8804);
	reg_w_val(gspca_dev->dev, mode, 0x8802);
}

static void i2c_write(struct gspca_dev *gspca_dev, __u16 valeur, __u16 reg)
{
	int retry = 60;
@@ -229,9 +178,9 @@ static void i2c_write(struct gspca_dev *gspca_dev, __u16 valeur, __u16 reg)

	DataLow = valeur;
	DataHight = valeur >> 8;
	reg_w_val(gspca_dev->dev, reg, 0x8801);
	reg_w_val(gspca_dev->dev, DataLow, 0x8805);
	reg_w_val(gspca_dev->dev, DataHight, 0x8800);
	reg_w_val(gspca_dev->dev, 0x8801, reg);
	reg_w_val(gspca_dev->dev, 0x8805, DataLow);
	reg_w_val(gspca_dev->dev, 0x8800, DataHight);
	while (retry--) {
		reg_r(gspca_dev, 0x8803, 1);
		if (!gspca_dev->usb_buf[0])
@@ -245,9 +194,9 @@ static int i2c_read(struct gspca_dev *gspca_dev, __u16 reg, __u8 mode)
	__u8 value;
	__u8 vallsb;

	reg_w_val(gspca_dev->dev, 0x92, 0x8804);
	reg_w_val(gspca_dev->dev, reg, 0x8801);
	reg_w_val(gspca_dev->dev, (mode | 0x01), 0x8802);
	reg_w_val(gspca_dev->dev, 0x8804, 0x92);
	reg_w_val(gspca_dev->dev, 0x8801, reg);
	reg_w_val(gspca_dev->dev, 0x8802, (mode | 0x01));
	while (retry--) {
		reg_r(gspca_dev, 0x8803, 1);
		if (!gspca_dev->usb_buf)
@@ -459,17 +408,6 @@ static const __u16 spca561_init_data[][2] = {
	{}
};

static void sensor_reset(struct gspca_dev *gspca_dev)
{
	reg_w_val(gspca_dev->dev, 0x8631, 0xc8);
	reg_w_val(gspca_dev->dev, 0x8634, 0xc8);
	reg_w_val(gspca_dev->dev, 0x8112, 0x00);
	reg_w_val(gspca_dev->dev, 0x8114, 0x00);
	reg_w_val(gspca_dev->dev, 0x8118, 0x21);
	i2c_init(gspca_dev, 0x14);
	i2c_write(gspca_dev, 1, 0x0d);
	i2c_write(gspca_dev, 0, 0x0d);
}

/******************** QC Express etch2 stuff ********************/
static const __u16 Pb100_1map8300[][2] = {
@@ -479,9 +417,9 @@ static const __u16 Pb100_1map8300[][2] = {
	{0x8303, 0x0125},	/* image area */
	{0x8304, 0x0169},
	{0x8328, 0x000b},
	{0x833c, 0x0001},
	{0x833c, 0x0001},		/*fixme: win:07*/

	{0x832f, 0x0419},
	{0x832f, 0x1904},		/*fixme: was 0419*/
	{0x8307, 0x00aa},
	{0x8301, 0x0003},
	{0x8302, 0x000e},
@@ -547,7 +485,7 @@ static const __u16 spca561_161rev12A_data2[][2] = {
	{0xdf, 0x863c},		/* df */
	{0xf0, 0x8505},
	{0x32, 0x850a},
	{0x99, 0x8700},		/* - white balance - new */
/*	{0x99, 0x8700},		 * - white balance - new (removed) */
	{}
};

@@ -566,9 +504,10 @@ static void sensor_mapwrite(struct gspca_dev *gspca_dev,
}
static void init_161rev12A(struct gspca_dev *gspca_dev)
{
	sensor_reset(gspca_dev);
/*	sensor_reset(gspca_dev);	(not in win) */
	write_vector(gspca_dev, spca561_161rev12A_data1);
	sensor_mapwrite(gspca_dev, Pb100_1map8300);
/*fixme: should be in sd_start*/
	write_vector(gspca_dev, spca561_161rev12A_data2);
	sensor_mapwrite(gspca_dev, Pb100_2map8300);
}
@@ -605,32 +544,29 @@ static int sd_config(struct gspca_dev *gspca_dev,
	cam->epaddr = 0x01;
	gspca_dev->nbalt = 7 + 1;	/* choose alternate 7 first */
	cam->cam_mode = sif_mode;
	cam->nmodes = sizeof sif_mode / sizeof sif_mode[0];
	cam->nmodes = ARRAY_SIZE(sif_mode);

	sd->chip_revision = id->driver_info;
	sd->brightness = BRIGHTNESS_DEF;
	sd->contrast = CONTRAST_DEF;
	sd->autogain = AUTOGAIN_DEF;
	sd->white = WHITE_DEF;
	sd->exposure = EXPOSURE_DEF;
	sd->autogain = AUTOGAIN_DEF;
	sd->gain = GAIN_DEF;
	return 0;
}

/* this function is called at open time */
static int sd_open(struct gspca_dev *gspca_dev)
static int sd_open_12a(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;

	switch (sd->chip_revision) {
	case Rev072A:
		PDEBUG(D_STREAM, "Chip revision id: 072a");
		write_vector(gspca_dev, spca561_init_data);
		break;
	default:
/*	case Rev012A: */
		PDEBUG(D_STREAM, "Chip revision id: 012a");
	PDEBUG(D_STREAM, "Chip revision: 012a");
	init_161rev12A(gspca_dev);
		break;
	return 0;
}
static int sd_open_72a(struct gspca_dev *gspca_dev)
{
	PDEBUG(D_STREAM, "Chip revision: 072a");
	write_vector(gspca_dev, spca561_init_data);
	return 0;
}

@@ -639,28 +575,20 @@ static void setcontrast(struct gspca_dev *gspca_dev)
	struct sd *sd = (struct sd *) gspca_dev;
	struct usb_device *dev = gspca_dev->dev;
	__u8 lowb;
	int expotimes;

	switch (sd->chip_revision) {
	case Rev072A:
		lowb = sd->contrast >> 8;
		reg_w_val(dev, lowb, 0x8651);
		reg_w_val(dev, lowb, 0x8652);
		reg_w_val(dev, lowb, 0x8653);
		reg_w_val(dev, lowb, 0x8654);
		reg_w_val(dev, 0x8651, lowb);
		reg_w_val(dev, 0x8652, lowb);
		reg_w_val(dev, 0x8653, lowb);
		reg_w_val(dev, 0x8654, lowb);
		break;
	default: {
/*	case Rev012A: { */
		__u8 Reg8391[] =
			{ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00 };

		/* Write camera sensor settings */
		expotimes = (sd->contrast >> 5) & 0x07ff;
		/* exposure is in 8309 2b, range 0120 - 5720 */
		Reg8391[0] = expotimes & 0xff;	/* exposure */
		Reg8391[1] = 0x18 | (expotimes >> 8);
		Reg8391[2] = sd->brightness;	/* gain */
		/* gain in 8335, 2b range 0000 - 2400 */
		static const __u8 Reg8391[] =
			{ 0x92, 0x30, 0x20, 0x00, 0x0c, 0x00, 0x00, 0x00 };

		reg_w_buf(gspca_dev, 0x8391, Reg8391, 8);
		reg_w_buf(gspca_dev, 0x8390, Reg8391, 8);
		break;
@@ -668,22 +596,17 @@ static void setcontrast(struct gspca_dev *gspca_dev)
	}
}

/* rev12a only */
static void setwhite(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	__u16 white;
	__u8 reg8614, reg8616;

	switch (sd->chip_revision) {
	case Rev072A:
		/* no such hardware */
		break;
	default:
/*	case Rev012A: */
	white = sd->white;
	if (sd->white == 0) {
		PDEBUG(D_CONF, "Discarding null whiteness");
			break;
		return;
	}
	/* try to emulate MS-win as possible */
	if (white < 0x45)
@@ -691,9 +614,26 @@ static void setwhite(struct gspca_dev *gspca_dev)
	else
		reg8616 = 0x93 + (white >> 2);
	reg8614 = 0x28 + (white >> 4);
		reg_w_val(gspca_dev->dev, reg8616, 0x8616);
		reg_w_val(gspca_dev->dev, reg8614, 0x8614);
	reg_w_val(gspca_dev->dev, 0x8616, reg8616);
	reg_w_val(gspca_dev->dev, 0x8614, reg8614);
}

/* rev 12a only */
static void setexposure(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	struct usb_device *dev = gspca_dev->dev;

	reg_w_val(dev, 0x8309, sd->gain);
}

/* rev 12a only */
static void setgain(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	struct usb_device *dev = gspca_dev->dev;

	reg_w_val(dev, 0x8335, sd->gain);
}

static void setautogain(struct gspca_dev *gspca_dev)
@@ -708,37 +648,14 @@ static void setautogain(struct gspca_dev *gspca_dev)
	}
}

static void sd_start(struct gspca_dev *gspca_dev)
static void sd_start_12a(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	struct usb_device *dev = gspca_dev->dev;
	int Clck;
	__u8 Reg8307[] = { 0xaa, 0x00 };
	int mode;

	mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
	switch (sd->chip_revision) {
	case Rev072A:
		switch (mode) {
		default:
/*		case 0:
		case 1: */
			Clck = 0x25;
			break;
		case 2:
			Clck = 0x22;
			break;
		case 3:
			Clck = 0x21;
			break;
		}
		reg_w_val(dev, 0x8500, mode);	/* mode */
		reg_w_val(dev, 0x8700, Clck);	/* 0x27 clock */
		reg_w_val(dev, 0x8112, 0x10 | 0x20);
		setautogain(gspca_dev);
		break;
	default:
/*	case Rev012A: */
	switch (mode) {
	case 0:
	case 1:
@@ -767,8 +684,31 @@ static void sd_start(struct gspca_dev *gspca_dev)
	reg_w_val(gspca_dev->dev, 0x850b, 0x03);
	setcontrast(gspca_dev);
	setwhite(gspca_dev);
}
static void sd_start_72a(struct gspca_dev *gspca_dev)
{
	struct usb_device *dev = gspca_dev->dev;
	int Clck;
	int mode;

	mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
	switch (mode) {
	default:
/*	case 0:
	case 1: */
		Clck = 0x25;
		break;
	case 2:
		Clck = 0x22;
		break;
	case 3:
		Clck = 0x21;
		break;
	}
	reg_w_val(dev, 0x8500, mode);	/* mode */
	reg_w_val(dev, 0x8700, Clck);	/* 0x27 clock */
	reg_w_val(dev, 0x8112, 0x10 | 0x20);
	setautogain(gspca_dev);
}

static void sd_stopN(struct gspca_dev *gspca_dev)
@@ -787,6 +727,7 @@ static void sd_close(struct gspca_dev *gspca_dev)
	reg_w_val(gspca_dev->dev, 0x8114, 0);
}

/* rev72a only */
static void do_autogain(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
@@ -895,24 +836,17 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev,
	gspca_frame_add(gspca_dev, INTER_PACKET, frame, data, len);
}

/* rev 72a only */
static void setbrightness(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	__u8 value;

	switch (sd->chip_revision) {
	case Rev072A:
	value = sd->brightness;
		reg_w_val(gspca_dev->dev, value, 0x8611);
		reg_w_val(gspca_dev->dev, value, 0x8612);
		reg_w_val(gspca_dev->dev, value, 0x8613);
		reg_w_val(gspca_dev->dev, value, 0x8614);
		break;
	default:
/*	case Rev012A: */
		setcontrast(gspca_dev);
		break;
	}
	reg_w_val(gspca_dev->dev, 0x8611, value);
	reg_w_val(gspca_dev->dev, 0x8612, value);
	reg_w_val(gspca_dev->dev, 0x8613, value);
	reg_w_val(gspca_dev->dev, 0x8614, value);
}

static void getbrightness(struct gspca_dev *gspca_dev)
@@ -920,8 +854,6 @@ static void getbrightness(struct gspca_dev *gspca_dev)
	struct sd *sd = (struct sd *) gspca_dev;
	__u16 tot;

	switch (sd->chip_revision) {
	case Rev072A:
	tot = 0;
	reg_r(gspca_dev, 0x8611, 1);
	tot += gspca_dev->usb_buf[0];
@@ -932,21 +864,14 @@ static void getbrightness(struct gspca_dev *gspca_dev)
	reg_r(gspca_dev, 0x8614, 1);
	tot += gspca_dev->usb_buf[0];
	sd->brightness = tot >> 2;
		break;
	default:
/*	case Rev012A: */
		/* no way to read sensor settings */
		break;
	}
}

/* rev72a only */
static void getcontrast(struct gspca_dev *gspca_dev)
{
	struct sd *sd = (struct sd *) gspca_dev;
	__u16 tot;

	switch (sd->chip_revision) {
	case Rev072A:
	tot = 0;
	reg_r(gspca_dev, 0x8651, 1);
	tot += gspca_dev->usb_buf[0];
@@ -957,15 +882,10 @@ static void getcontrast(struct gspca_dev *gspca_dev)
	reg_r(gspca_dev, 0x8654, 1);
	tot += gspca_dev->usb_buf[0];
	sd->contrast = tot << 6;
		break;
	default:
/*	case Rev012A: */
		/* no way to read sensor settings */
		break;
	}
	PDEBUG(D_CONF, "get contrast %d", sd->contrast);
}

/* rev 72a only */
static int sd_setbrightness(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;
@@ -985,6 +905,7 @@ static int sd_getbrightness(struct gspca_dev *gspca_dev, __s32 *val)
	return 0;
}

/* rev 72a only */
static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;
@@ -1022,7 +943,7 @@ static int sd_getautogain(struct gspca_dev *gspca_dev, __s32 *val)
	return 0;
}

/* white balance - new */
/* rev12a only */
static int sd_setwhite(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;
@@ -1041,20 +962,173 @@ static int sd_getwhite(struct gspca_dev *gspca_dev, __s32 *val)
	return 0;
}

/* rev12a only */
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;
}

/* rev12a only */
static int sd_setgain(struct gspca_dev *gspca_dev, __s32 val)
{
	struct sd *sd = (struct sd *) gspca_dev;

	sd->gain = val;
	if (gspca_dev->streaming)
		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;
}

/* control tables */
static struct ctrl sd_ctrls_12a[] = {
	{
	    {
		.id = V4L2_CID_DO_WHITE_BALANCE,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "While Balance",
		.minimum = WHITE_MIN,
		.maximum = WHITE_MAX,
		.step = 1,
		.default_value = WHITE_DEF,
	    },
	    .set = sd_setwhite,
	    .get = sd_getwhite,
	},
	{
	    {
		.id = V4L2_CID_EXPOSURE,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Exposure",
		.minimum = EXPOSURE_MIN,
		.maximum = EXPOSURE_MAX,
		.step = 1,
		.default_value = EXPOSURE_DEF,
	    },
	    .set = sd_setexposure,
	    .get = sd_getexposure,
	},
	{
	    {
		.id = V4L2_CID_AUTOGAIN,
		.type = V4L2_CTRL_TYPE_BOOLEAN,
		.name = "Auto Gain",
		.minimum = AUTOGAIN_MIN,
		.maximum = AUTOGAIN_MAX,
		.step = 1,
		.default_value = AUTOGAIN_DEF,
	    },
	    .set = sd_setautogain,
	    .get = sd_getautogain,
	},
	{
	    {
		.id = V4L2_CID_GAIN,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Gain",
		.minimum = GAIN_MIN,
		.maximum = GAIN_MAX,
		.step = 1,
		.default_value = GAIN_DEF,
	    },
	    .set = sd_setgain,
	    .get = sd_getgain,
	},
};

static struct ctrl sd_ctrls_72a[] = {
	{
	   {
		.id = V4L2_CID_BRIGHTNESS,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Brightness",
		.minimum = BRIGHTNESS_MIN,
		.maximum = BRIGHTNESS_MAX,
		.step = 1,
		.default_value = BRIGHTNESS_DEF,
	    },
	    .set = sd_setbrightness,
	    .get = sd_getbrightness,
	},
	{
	    {
		.id = V4L2_CID_CONTRAST,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Contrast",
		.minimum = CONTRAST_MIN,
		.maximum = CONTRAST_MAX,
		.step = 1,
		.default_value = CONTRAST_DEF,
	    },
	    .set = sd_setcontrast,
	    .get = sd_getcontrast,
	},
	{
	    {
		.id = V4L2_CID_AUTOGAIN,
		.type = V4L2_CTRL_TYPE_BOOLEAN,
		.name = "Auto Gain",
		.minimum = AUTOGAIN_MIN,
		.maximum = AUTOGAIN_MAX,
		.step = 1,
		.default_value = AUTOGAIN_DEF,
	    },
	    .set = sd_setautogain,
	    .get = sd_getautogain,
	},
};

/* sub-driver description */
static const struct sd_desc sd_desc = {
static const struct sd_desc sd_desc_12a = {
	.name = MODULE_NAME,
	.ctrls = sd_ctrls,
	.nctrls = ARRAY_SIZE(sd_ctrls),
	.ctrls = sd_ctrls_12a,
	.nctrls = ARRAY_SIZE(sd_ctrls_12a),
	.config = sd_config,
	.open = sd_open,
	.start = sd_start,
	.open = sd_open_12a,
	.start = sd_start_12a,
	.stopN = sd_stopN,
	.stop0 = sd_stop0,
	.close = sd_close,
	.pkt_scan = sd_pkt_scan,
/*	.dq_callback = do_autogain,	 * fixme */
};
static const struct sd_desc sd_desc_72a = {
	.name = MODULE_NAME,
	.ctrls = sd_ctrls_72a,
	.nctrls = ARRAY_SIZE(sd_ctrls_72a),
	.config = sd_config,
	.open = sd_open_72a,
	.start = sd_start_72a,
	.stopN = sd_stopN,
	.stop0 = sd_stop0,
	.close = sd_close,
	.pkt_scan = sd_pkt_scan,
	.dq_callback = do_autogain,
};
static const struct sd_desc *sd_desc[2] = {
	&sd_desc_12a,
	&sd_desc_72a
};

/* -- module initialisation -- */
static const __devinitdata struct usb_device_id device_table[] = {
@@ -1082,7 +1156,9 @@ MODULE_DEVICE_TABLE(usb, device_table);
static int sd_probe(struct usb_interface *intf,
		    const struct usb_device_id *id)
{
	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
	return gspca_dev_probe(intf, id,
				sd_desc[id->driver_info],
				sizeof(struct sd),
			       THIS_MODULE);
}