Here's an updated version of the camera driver (against current OLPC

Jonathan Corbet corbet at lwn.unroutablenet
Mon Nov 27 13:15:18 EST 2006


Commit:     ad1f5b7592bc8d5eec68a5f0655e10329fe1d7aa
Parent:     9f9151dbc02eb20bc5b1485f62867821f20fa4a2
commit ad1f5b7592bc8d5eec68a5f0655e10329fe1d7aa
Author:     Jonathan Corbet <corbet at lwn.net>
AuthorDate: Mon Nov 27 13:20:07 2006 -0500
Commit:     Andres Salomon <dilinger at debian.org>
CommitDate: Mon Nov 27 13:20:07 2006 -0500

    Here's an updated version of the camera driver (against current OLPC
    git) for your pleasure.  There's a couple of fixes, but the biggest
    change is support for the hue and saturation controls - something which
    was rather harder than I thought it should be...
    
    Signed-off-by: Andres Salomon <dilinger at debian.org>
---
 drivers/media/video/cafe_ccic.c |    8 +
 drivers/media/video/ov7670.c    |  345 ++++++++++++++++++++++++++++++++++-----
 2 files changed, 308 insertions(+), 45 deletions(-)

diff --git a/drivers/media/video/cafe_ccic.c b/drivers/media/video/cafe_ccic.c
index d68c9f8..67c2478 100644
--- a/drivers/media/video/cafe_ccic.c
+++ b/drivers/media/video/cafe_ccic.c
@@ -521,7 +521,6 @@ static int cafe_smbus_attach(struct i2c_
 	/*
 	 * Don't talk to chips we don't recognize.
 	 */
-	cam_err(cam, "smbus_attach id = %d\n", client->driver->id);
 	if (client->driver->id == I2C_DRIVERID_OV7670) {
 		cam->sensor = client;
 		return cafe_cam_init(cam);
@@ -1481,8 +1480,11 @@ static int cafe_v4l_release(struct inode
 		cafe_free_sio_buffers(cam);
 		cam->owner = NULL;
 	}
-	if (cam->users == 0)
+	if (cam->users == 0) {
 		cafe_ctlr_power_down(cam);
+		if (! alloc_bufs_at_load)
+			cafe_free_dma_bufs(cam);
+	}
 	mutex_unlock(&cam->s_mutex);
 	return 0;
 }
@@ -1701,7 +1703,7 @@ static struct v4l2_tvnorm cafe_tvnorm[] 
 };
 
 
-void cafe_v4l_dev_release(struct video_device *vd)
+static void cafe_v4l_dev_release(struct video_device *vd)
 {
 	struct cafe_camera *cam = container_of(vd, struct cafe_camera, v4ldev);
 
diff --git a/drivers/media/video/ov7670.c b/drivers/media/video/ov7670.c
index 466d6e9..7206b8e 100644
--- a/drivers/media/video/ov7670.c
+++ b/drivers/media/video/ov7670.c
@@ -138,6 +138,20 @@ #define REG_COM17	0x42	/* Control 17 */
 #define   COM17_AECWIN	  0xc0	  /* AEC window - must match COM4 */
 #define   COM17_CBAR	  0x08	  /* DSP Color bar */
 
+/*
+ * This matrix defines how the colors are generated, must be
+ * tweaked to adjust hue and saturation.
+ *
+ * Order: v-red, v-green, v-blue, u-red, u-green, u-blue
+ *
+ * They are nine-bit signed quantities, with the sign bit
+ * stored in 0x58.  Sign for v-red is bit 0, and up from there.
+ */
+#define	REG_CMATRIX_BASE 0x4f
+#define   CMATRIX_LEN 6
+#define REG_CMATRIX_SIGN 0x58
+ 
+
 #define REG_BRIGHT	0x55	/* Brightness */
 #define REG_CONTRAS	0x56	/* Contrast control */
 
@@ -160,6 +174,19 @@ #define REG_BD60MAX	0xab	/* 60hz banding
 
 
 /*
+ * Information we maintain about a known sensor.
+ */
+struct ov7670_format_struct;  /* coming later */
+struct ov7670_info {
+	struct ov7670_format_struct *fmt;  /* Current format */
+	unsigned char sat;	       	/* Saturation value */
+	int hue;			/* Hue value */
+};
+
+
+
+
+/*
  * The default register settings, as obtained from OmniVision.  There
  * is really no making sense of most of these - lots of "reserved" values
  * and such.
@@ -308,6 +335,7 @@ static struct regval_list ov7670_fmt_yuv
 	{ REG_COM9, 0x18 }, /* 4x gain ceiling; 0x8 is reserved bit */
 	{ 0x4f, 0x80 }, 	/* "matrix coefficient 1" */
 	{ 0x50, 0x80 }, 	/* "matrix coefficient 2" */
+	{ 0x51, 0    },		/* vb */
 	{ 0x52, 0x22 }, 	/* "matrix coefficient 4" */
 	{ 0x53, 0x5e }, 	/* "matrix coefficient 5" */
 	{ 0x54, 0x80 }, 	/* "matrix coefficient 6" */
@@ -323,6 +351,7 @@ static struct regval_list ov7670_fmt_rgb
 	{ REG_COM9, 0x38 }, 	/* 16x gain ceiling; 0x8 is reserved bit */
 	{ 0x4f, 0xb3 }, 	/* "matrix coefficient 1" */
 	{ 0x50, 0xb3 }, 	/* "matrix coefficient 2" */
+	{ 0x51, 0    },		/* vb */
 	{ 0x52, 0x3d }, 	/* "matrix coefficient 4" */
 	{ 0x53, 0xa7 }, 	/* "matrix coefficient 5" */
 	{ 0x54, 0xe4 }, 	/* "matrix coefficient 6" */
@@ -338,6 +367,7 @@ static struct regval_list ov7670_fmt_rgb
 	{ REG_COM9, 0x38 }, 	/* 16x gain ceiling; 0x8 is reserved bit */
 	{ 0x4f, 0xb3 }, 	/* "matrix coefficient 1" */
 	{ 0x50, 0xb3 }, 	/* "matrix coefficient 2" */
+	{ 0x51, 0    },		/* vb */
 	{ 0x52, 0x3d }, 	/* "matrix coefficient 4" */
 	{ 0x53, 0xa7 }, 	/* "matrix coefficient 5" */
 	{ 0x54, 0xe4 }, 	/* "matrix coefficient 6" */
@@ -358,7 +388,7 @@ static int ov7670_read(struct i2c_client
 	int ret;
 
 	ret = i2c_smbus_read_byte_data(c, reg);
-	if (ret > 0)
+	if (ret >= 0)
 		*value = (unsigned char) ret;
 	return ret;
 }
@@ -379,16 +409,13 @@ static int ov7670_write_mask(struct i2c_
 	int ret, tries = 0;
 
 	ret = ov7670_read(c, reg, &v);
-	printk(KERN_ERR "ovwm read %x %d\n", v, ret);
 	if (ret < 0)
 		return ret;
 	v &= ~mask;
 	v |= (value & mask);
 	msleep(10); /* FIXME experiment */
-	printk(KERN_ERR "To write %x\n", v);
 	do {
 		ret = ov7670_write(c, reg, v);
-		printk(KERN_ERR "write status %d\n", ret);
 	} while (ret < 0 && ++tries < 3);
 	return ret;
 }
@@ -462,30 +489,35 @@ static int ov7670_detect(struct i2c_clie
 }
 
 
-
-
-
+/*
+ * Store information about the video data format.  The color matrix
+ * is deeply tied into the format, so keep the relevant values here.
+ * The magic matrix nubmers come from OmniVision.
+ */
 static struct ov7670_format_struct {
 	__u8 *desc;
 	__u32 pixelformat;
 	struct regval_list *regs;
+	int cmatrix[CMATRIX_LEN];
 } ov7670_formats[] = {
 	{
 		.desc		= "YUYV 4:2:2",
 		.pixelformat	= V4L2_PIX_FMT_YUYV,
 		.regs 		= ov7670_fmt_yuv422,
+		.cmatrix	= { 128, -128, 0, -34, -94, 128 },
 	},
 	{
 		.desc		= "RGB 444",
 		.pixelformat	= V4L2_PIX_FMT_RGB444,
 		.regs		= ov7670_fmt_rgb444,
+		.cmatrix	= { 179, -179, 0, -61, -176, 228 },
 	},
 	{
 		.desc		= "RGB 565",
 		.pixelformat	= V4L2_PIX_FMT_RGB565,
 		.regs		= ov7670_fmt_rgb565,
+		.cmatrix	= { 179, -179, 0, -61, -176, 228 },
 	},
-#if 0
 	/*
 	 * Pretend we do RGB32.  This is here on the assumption that the
 	 * upper layer will reformat RGB444 appropriately.
@@ -497,8 +529,8 @@ #if 0
 		.desc		= "RGB32 (faked)",
 		.pixelformat	= V4L2_PIX_FMT_RGB32,
 		.regs		= ov7670_fmt_rgb444,
+		.cmatrix	= { 179, -179, 0, -61, -176, 228 },
 	},
-#endif
 };
 #define N_OV7670_FMTS (sizeof(ov7670_formats)/sizeof(ov7670_formats[0]))
 
@@ -532,7 +564,6 @@ static struct regval_list ov7670_qcif_re
 	{ 0x74, 0x19 },
 	{ 0x9a, 0x80 },
 	{ 0x43, 0x14 },
-	{ REG_RED, 0x60 },
 	{ REG_COM13, 0xc0 },
 	{ 0xff, 0xff },
 };
@@ -698,6 +729,7 @@ static int ov7670_s_fmt(struct i2c_clien
 	int ret;
 	struct ov7670_format_struct *ovfmt;
 	struct ov7670_win_size *wsize;
+	struct ov7670_info *info = i2c_get_clientdata(c);
 	unsigned char com7;
 
 	ret = ov7670_try_fmt(c, fmt, &ovfmt, &wsize);
@@ -721,6 +753,7 @@ static int ov7670_s_fmt(struct i2c_clien
 	ret = 0;
 	if (wsize->regs)
 		ret = ov7670_write_array(c, wsize->regs);
+	info->fmt = ovfmt;
 	return 0;
 }
 
@@ -728,6 +761,190 @@ static int ov7670_s_fmt(struct i2c_clien
  * Code for dealing with controls.
  */
 
+#if 0  /* This seems unneeded after all, should probably come out */
+/*
+ * Fetch and store the color matrix.
+ */
+static int ov7670_get_cmatrix(struct i2c_client *client,
+	int matrix[CMATRIX_LEN])
+{
+	int i, ret;
+	unsigned char signbits;
+
+	ret = ov7670_read(client, REG_CMATRIX_SIGN, &signbits);
+	for (i = 0; i < CMATRIX_LEN; i++) {
+		unsigned char raw;
+
+		ret += ov7670_read(client, REG_CMATRIX_BASE + i, &raw);
+		matrix[i] = (int) raw;
+		if (signbits & (1 << i))
+			matrix[i] *= -1;
+	}
+	return ret;
+}
+#endif
+
+
+
+
+static int ov7670_store_cmatrix(struct i2c_client *client,
+		int matrix[CMATRIX_LEN])
+{
+	int i, ret;
+	unsigned char signbits;
+
+	/*
+	 * Weird crap seems to exist in the upper part of
+	 * the sign bits register, so let's preserve it.
+	 */
+	ret = ov7670_read(client, REG_CMATRIX_SIGN, &signbits);
+	signbits &= 0xc0;
+
+	for (i = 0; i < CMATRIX_LEN; i++) {
+		unsigned char raw;
+
+		if (matrix[i] < 0) {
+			signbits |= (1 << i);
+			if (matrix[i] < -255)
+				raw = 0xff;
+			else
+				raw = (-1 * matrix[i]) & 0xff;
+		}
+		else {
+			if (matrix[i] > 255)
+				raw = 0xff;
+			else
+				raw = matrix[i] & 0xff;
+		}
+		ret += ov7670_write(client, REG_CMATRIX_BASE + i, raw);
+	}
+	ret += ov7670_write(client, REG_CMATRIX_SIGN, signbits);
+	return ret;
+}
+
+
+/*
+ * Hue also requires messing with the color matrix.  It also requires
+ * trig functions, which tend not to be well supported in the kernel.
+ * So here is a simple table of sine values, 0-90 degrees, in steps
+ * of five degrees.  Values are multiplied by 1000.
+ *
+ * The following naive approximate trig functions require an argument
+ * carefully limited to -180 <= theta <= 180.
+ */
+#define SIN_STEP 5
+static const int ov7670_sin_table[] = {
+	   0,	 87,   173,   258,   342,   422,
+	 499,	573,   642,   707,   766,   819,
+	 866,	906,   939,   965,   984,   996,
+	1000
+};
+
+static int ov7670_sine(int theta)
+{
+	int chs = 1;
+	int sine;
+
+	if (theta < 0) {
+		theta = -theta;
+		chs = -1;
+	}
+	if (theta <= 90)
+		sine = ov7670_sin_table[theta/SIN_STEP];
+	else {
+		theta -= 90;
+		sine = 1000 - ov7670_sin_table[theta/SIN_STEP];
+	}
+	return sine*chs;
+}
+
+static int ov7670_cosine(int theta)
+{
+	theta = 90 - theta;
+	if (theta > 180)
+		theta -= 360;
+	else if (theta < -180)
+		theta += 360;
+	return ov7670_sine(theta);
+}
+
+
+
+
+static void ov7670_calc_cmatrix(struct ov7670_info *info,
+		int matrix[CMATRIX_LEN])
+{
+	int i;
+	/*
+	 * Apply the current saturation setting first.
+	 */
+	for (i = 0; i < CMATRIX_LEN; i++)
+		matrix[i] = (info->fmt->cmatrix[i]*info->sat) >> 7;
+	/*
+	 * Then, if need be, rotate the hue value.
+	 */
+	if (info->hue != 0) {
+		int sinth, costh, tmpmatrix[CMATRIX_LEN];
+
+		memcpy(tmpmatrix, matrix, CMATRIX_LEN*sizeof(int));
+		sinth = ov7670_sine(info->hue);
+		costh = ov7670_cosine(info->hue);
+
+		matrix[0] = (matrix[3]*sinth + matrix[0]*costh)/1000;
+		matrix[1] = (matrix[4]*sinth + matrix[1]*costh)/1000;
+		matrix[2] = (matrix[5]*sinth + matrix[2]*costh)/1000;
+		matrix[3] = (matrix[3]*costh - matrix[0]*sinth)/1000;
+		matrix[4] = (matrix[4]*costh - matrix[1]*sinth)/1000;
+		matrix[5] = (matrix[5]*costh - matrix[2]*sinth)/1000;
+	}
+}
+
+
+
+static int ov7670_t_sat(struct i2c_client *client, int value)
+{
+	struct ov7670_info *info = i2c_get_clientdata(client);
+	int matrix[CMATRIX_LEN];
+	int ret;
+
+	info->sat = value;
+	ov7670_calc_cmatrix(info, matrix);
+	ret = ov7670_store_cmatrix(client, matrix);
+	return ret;
+}
+
+static int ov7670_q_sat(struct i2c_client *client, __s32 *value)
+{
+	struct ov7670_info *info = i2c_get_clientdata(client);
+
+	*value = info->sat;
+	return 0;
+}
+
+static int ov7670_t_hue(struct i2c_client *client, int value)
+{
+	struct ov7670_info *info = i2c_get_clientdata(client);
+	int matrix[CMATRIX_LEN];
+	int ret;
+
+	if (value < -180 || value > 180)
+		return -EINVAL;
+	info->hue = value;
+	ov7670_calc_cmatrix(info, matrix);
+	ret = ov7670_store_cmatrix(client, matrix);
+	return ret;
+}
+	
+
+static int ov7670_q_hue(struct i2c_client *client, __s32 *value)
+{
+	struct ov7670_info *info = i2c_get_clientdata(client);
+
+	*value = info->hue;
+	return 0;
+}
+
+
 /*
  * Some weird registers seem to store values in a sign/magnitude format!
  */
@@ -748,38 +965,43 @@ static unsigned char ov7670_abs_to_sm(un
 		return (128 - v) | 0x80;
 }
 
-static int ov7670_t_brightness(struct i2c_client *client, unsigned char value)
+static int ov7670_t_brightness(struct i2c_client *client, int value)
 {
-	unsigned char com8;
+	unsigned char com8, v;
 	int ret;
 
 	ov7670_read(client, REG_COM8, &com8);
 	com8 &= ~COM8_AEC;
 	ov7670_write(client, REG_COM8, com8);
-	value = ov7670_abs_to_sm(value);
-	ret = ov7670_write(client, REG_BRIGHT, value);
+	v = ov7670_abs_to_sm(value);
+	ret = ov7670_write(client, REG_BRIGHT, v);
 	return ret;
 }
 
-static int ov7670_q_brightness(struct i2c_client *client, unsigned char *value)
+static int ov7670_q_brightness(struct i2c_client *client, __s32 *value)
 {
-	int ret;
-	ret = ov7670_read(client, REG_BRIGHT, value);
-	*value = ov7670_sm_to_abs(*value);
+	unsigned char v;
+	int ret = ov7670_read(client, REG_BRIGHT, &v);
+	
+	*value = ov7670_sm_to_abs(v);
 	return ret;
 }
 
-static int ov7670_t_contrast(struct i2c_client *client, unsigned char value)
+static int ov7670_t_contrast(struct i2c_client *client, int value)
 {
-	return ov7670_write(client, REG_CONTRAS, value);
+	return ov7670_write(client, REG_CONTRAS, (unsigned char) value);
 }
 
-static int ov7670_q_contrast(struct i2c_client *client, unsigned char *value)
+static int ov7670_q_contrast(struct i2c_client *client, __s32 *value)
 {
-	return ov7670_read(client, REG_CONTRAS, value);
+	unsigned char v;
+	int ret = ov7670_read(client, REG_CONTRAS, &v);
+
+	*value = v;
+	return ret;
 }
 
-static int ov7670_q_hflip(struct i2c_client *client, unsigned char *value)
+static int ov7670_q_hflip(struct i2c_client *client, __s32 *value)
 {
 	int ret;
 	unsigned char v;
@@ -790,7 +1012,7 @@ static int ov7670_q_hflip(struct i2c_cli
 }
 
 
-static int ov7670_t_hflip(struct i2c_client *client, unsigned char value)
+static int ov7670_t_hflip(struct i2c_client *client, int value)
 {
 	unsigned char v;
 	int ret;
@@ -807,7 +1029,7 @@ static int ov7670_t_hflip(struct i2c_cli
 
 
 
-static int ov7670_q_vflip(struct i2c_client *client, unsigned char *value)
+static int ov7670_q_vflip(struct i2c_client *client, __s32 *value)
 {
 	int ret;
 	unsigned char v;
@@ -818,7 +1040,7 @@ static int ov7670_q_vflip(struct i2c_cli
 }
 
 
-static int ov7670_t_vflip(struct i2c_client *client, unsigned char value)
+static int ov7670_t_vflip(struct i2c_client *client, int value)
 {
 	unsigned char v;
 	int ret;
@@ -836,8 +1058,8 @@ static int ov7670_t_vflip(struct i2c_cli
 
 static struct ov7670_control {
 	struct v4l2_queryctrl qc;
-	int (*query)(struct i2c_client *c, unsigned char *value);
-	int (*tweak)(struct i2c_client *c, unsigned char value);
+	int (*query)(struct i2c_client *c, __s32 *value);
+	int (*tweak)(struct i2c_client *c, int value);
 } ov7670_controls[] =
 {
 	{
@@ -870,6 +1092,34 @@ static struct ov7670_control {
 	},
 	{
 		.qc = {
+			.id = V4L2_CID_SATURATION,
+			.type = V4L2_CTRL_TYPE_INTEGER,
+			.name = "Saturation",
+			.minimum = 0,
+			.maximum = 256,
+			.step = 1,
+			.default_value = 0x80,
+			.flags = V4L2_CTRL_FLAG_SLIDER
+		},
+		.tweak = ov7670_t_sat,
+		.query = ov7670_q_sat,
+	},
+	{
+		.qc = {
+			.id = V4L2_CID_HUE,
+			.type = V4L2_CTRL_TYPE_INTEGER,
+			.name = "HUE",
+			.minimum = -180,
+			.maximum = 180,
+			.step = 5,
+			.default_value = 0,
+			.flags = V4L2_CTRL_FLAG_SLIDER
+		},
+		.tweak = ov7670_t_hue,
+		.query = ov7670_q_hue,
+	},
+	{
+		.qc = {
 			.id = V4L2_CID_VFLIP,
 			.type = V4L2_CTRL_TYPE_BOOLEAN,
 			.name = "Vertical flip",
@@ -923,25 +1173,26 @@ static int ov7670_g_ctrl(struct i2c_clie
 {
 	struct ov7670_control *octrl = ov7670_find_control(ctrl->id);
 	int ret;
-	unsigned char v;
 
 	if (octrl == NULL)
 		return -EINVAL;
-	ret = octrl->query(client, &v);
-	if (ret >= 0) {
-		ctrl->value = v;
+	ret = octrl->query(client, &ctrl->value);
+	if (ret >= 0)
 		return 0;
-	}
 	return ret;
 }
 
 static int ov7670_s_ctrl(struct i2c_client *client, struct v4l2_control *ctrl)
 {
 	struct ov7670_control *octrl = ov7670_find_control(ctrl->id);
+	int ret;
 
 	if (octrl == NULL)
 		return -EINVAL;
-	return octrl->tweak(client, ctrl->value);
+	ret =  octrl->tweak(client, ctrl->value);
+	if (ret >= 0)
+		return 0;
+	return ret;
 }
 
 
@@ -949,7 +1200,6 @@ static int ov7670_s_ctrl(struct i2c_clie
 
 
 
-
 /*
  * Basic i2c stuff.
  */
@@ -959,15 +1209,14 @@ static int ov7670_attach(struct i2c_adap
 {
 	int ret;
 	struct i2c_client *client;
-
-	printk(KERN_ERR "ov7670 attach, id = %d\n", adapter->id);
+	struct ov7670_info *info;
+	
 	/*
 	 * For now: only deal with adapters we recognize.
 	 */
 	if (adapter->id != I2C_HW_SMBUS_CAFE)
 		return -ENODEV;
 
-	printk(KERN_ERR "ov7670 accepting\n");
 	client = kzalloc(sizeof (struct i2c_client), GFP_KERNEL);
 	if (! client)
 		return -ENOMEM;
@@ -975,18 +1224,29 @@ static int ov7670_attach(struct i2c_adap
 	client->addr = OV7670_I2C_ADDR;
 	client->driver = &ov7670_driver,
 	strcpy(client->name, "OV7670");
-	/* Do we need clientdata? */
-
+	/*
+	 * Set up our info structure.
+	 */
+	info = kzalloc(sizeof (struct ov7670_info), GFP_KERNEL);
+	if (! info) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+	info->fmt = &ov7670_formats[0];
+	info->sat = 128;	/* Review this */
+	i2c_set_clientdata(client, info);
+	
 	/*
 	 * Make sure it's an ov7670
 	 */
 	ret = ov7670_detect(client);
-	printk(KERN_ERR "detect result is %d\n", ret);
 	if (ret)
-		goto out_free;
+		goto out_free_info;
 	i2c_attach_client(client);
 	return 0;
 
+  out_free_info:
+	kfree(info);
   out_free:
 	kfree(client);
 	return ret;
@@ -996,6 +1256,7 @@ static int ov7670_attach(struct i2c_adap
 static int ov7670_detach(struct i2c_client *client)
 {
 	i2c_detach_client(client);
+	kfree(i2c_get_clientdata(client));
 	kfree(client);
 	return 0;
 }


More information about the Commits-kernel mailing list