mirror of https://gitee.com/openkylin/linux.git
[media] ov7670: allow configuration of image size, clock speed, and I/O method
These parameters need to be configurable based on the host system. They can now be communicated through the s_config call. The old CONFIG_OLPC_XO_1 selector was not correct; this kind of arrangement wouldn't allow for a universal kernel that would work on both laptops. Certain parts of the probe routine had to be moved later (into s_config), because we can't do any I/O until we know which I/O method has been selected through this mechanism. Signed-off-by: Daniel Drake <dsd@laptop.org> Acked-by: Jonathan Corbet <corbet@lwn.net> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
This commit is contained in:
parent
f8c6127443
commit
75e2bdad89
|
@ -20,6 +20,7 @@
|
||||||
#include <media/v4l2-chip-ident.h>
|
#include <media/v4l2-chip-ident.h>
|
||||||
#include <media/v4l2-mediabus.h>
|
#include <media/v4l2-mediabus.h>
|
||||||
|
|
||||||
|
#include "ov7670.h"
|
||||||
|
|
||||||
MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>");
|
MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>");
|
||||||
MODULE_DESCRIPTION("A low-level driver for OmniVision ov7670 sensors");
|
MODULE_DESCRIPTION("A low-level driver for OmniVision ov7670 sensors");
|
||||||
|
@ -42,11 +43,6 @@ MODULE_PARM_DESC(debug, "Debug level (0-1)");
|
||||||
#define QCIF_WIDTH 176
|
#define QCIF_WIDTH 176
|
||||||
#define QCIF_HEIGHT 144
|
#define QCIF_HEIGHT 144
|
||||||
|
|
||||||
/*
|
|
||||||
* Our nominal (default) frame rate.
|
|
||||||
*/
|
|
||||||
#define OV7670_FRAME_RATE 30
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The 7670 sits on i2c with ID 0x42
|
* The 7670 sits on i2c with ID 0x42
|
||||||
*/
|
*/
|
||||||
|
@ -198,7 +194,11 @@ struct ov7670_info {
|
||||||
struct ov7670_format_struct *fmt; /* Current format */
|
struct ov7670_format_struct *fmt; /* Current format */
|
||||||
unsigned char sat; /* Saturation value */
|
unsigned char sat; /* Saturation value */
|
||||||
int hue; /* Hue value */
|
int hue; /* Hue value */
|
||||||
|
int min_width; /* Filter out smaller sizes */
|
||||||
|
int min_height; /* Filter out smaller sizes */
|
||||||
|
int clock_speed; /* External clock speed (MHz) */
|
||||||
u8 clkrc; /* Clock divider value */
|
u8 clkrc; /* Clock divider value */
|
||||||
|
bool use_smbus; /* Use smbus I/O instead of I2C */
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline struct ov7670_info *to_state(struct v4l2_subdev *sd)
|
static inline struct ov7670_info *to_state(struct v4l2_subdev *sd)
|
||||||
|
@ -415,8 +415,7 @@ static struct regval_list ov7670_fmt_raw[] = {
|
||||||
* ov7670 is not really an SMBUS device, though, so the communication
|
* ov7670 is not really an SMBUS device, though, so the communication
|
||||||
* is not always entirely reliable.
|
* is not always entirely reliable.
|
||||||
*/
|
*/
|
||||||
#ifdef CONFIG_OLPC_XO_1
|
static int ov7670_read_smbus(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg,
|
|
||||||
unsigned char *value)
|
unsigned char *value)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||||
|
@ -431,7 +430,7 @@ static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg,
|
static int ov7670_write_smbus(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
unsigned char value)
|
unsigned char value)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||||
|
@ -442,11 +441,10 @@ static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else /* ! CONFIG_OLPC_XO_1 */
|
|
||||||
/*
|
/*
|
||||||
* On most platforms, we'd rather do straight i2c I/O.
|
* On most platforms, we'd rather do straight i2c I/O.
|
||||||
*/
|
*/
|
||||||
static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg,
|
static int ov7670_read_i2c(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
unsigned char *value)
|
unsigned char *value)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||||
|
@ -479,7 +477,7 @@ static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg,
|
static int ov7670_write_i2c(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
unsigned char value)
|
unsigned char value)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||||
|
@ -498,8 +496,26 @@ static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
msleep(5); /* Wait for reset to run */
|
msleep(5); /* Wait for reset to run */
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_OLPC_XO_1 */
|
|
||||||
|
|
||||||
|
static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
|
unsigned char *value)
|
||||||
|
{
|
||||||
|
struct ov7670_info *info = to_state(sd);
|
||||||
|
if (info->use_smbus)
|
||||||
|
return ov7670_read_smbus(sd, reg, value);
|
||||||
|
else
|
||||||
|
return ov7670_read_i2c(sd, reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg,
|
||||||
|
unsigned char value)
|
||||||
|
{
|
||||||
|
struct ov7670_info *info = to_state(sd);
|
||||||
|
if (info->use_smbus)
|
||||||
|
return ov7670_write_smbus(sd, reg, value);
|
||||||
|
else
|
||||||
|
return ov7670_write_i2c(sd, reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write a list of register settings; ff/ff stops the process.
|
* Write a list of register settings; ff/ff stops the process.
|
||||||
|
@ -854,7 +870,7 @@ static int ov7670_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms)
|
||||||
memset(cp, 0, sizeof(struct v4l2_captureparm));
|
memset(cp, 0, sizeof(struct v4l2_captureparm));
|
||||||
cp->capability = V4L2_CAP_TIMEPERFRAME;
|
cp->capability = V4L2_CAP_TIMEPERFRAME;
|
||||||
cp->timeperframe.numerator = 1;
|
cp->timeperframe.numerator = 1;
|
||||||
cp->timeperframe.denominator = OV7670_FRAME_RATE;
|
cp->timeperframe.denominator = info->clock_speed;
|
||||||
if ((info->clkrc & CLK_EXT) == 0 && (info->clkrc & CLK_SCALE) > 1)
|
if ((info->clkrc & CLK_EXT) == 0 && (info->clkrc & CLK_SCALE) > 1)
|
||||||
cp->timeperframe.denominator /= (info->clkrc & CLK_SCALE);
|
cp->timeperframe.denominator /= (info->clkrc & CLK_SCALE);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -875,14 +891,14 @@ static int ov7670_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms)
|
||||||
if (tpf->numerator == 0 || tpf->denominator == 0)
|
if (tpf->numerator == 0 || tpf->denominator == 0)
|
||||||
div = 1; /* Reset to full rate */
|
div = 1; /* Reset to full rate */
|
||||||
else
|
else
|
||||||
div = (tpf->numerator*OV7670_FRAME_RATE)/tpf->denominator;
|
div = (tpf->numerator * info->clock_speed) / tpf->denominator;
|
||||||
if (div == 0)
|
if (div == 0)
|
||||||
div = 1;
|
div = 1;
|
||||||
else if (div > CLK_SCALE)
|
else if (div > CLK_SCALE)
|
||||||
div = CLK_SCALE;
|
div = CLK_SCALE;
|
||||||
info->clkrc = (info->clkrc & 0x80) | div;
|
info->clkrc = (info->clkrc & 0x80) | div;
|
||||||
tpf->numerator = 1;
|
tpf->numerator = 1;
|
||||||
tpf->denominator = OV7670_FRAME_RATE/div;
|
tpf->denominator = info->clock_speed / div;
|
||||||
return ov7670_write(sd, REG_CLKRC, info->clkrc);
|
return ov7670_write(sd, REG_CLKRC, info->clkrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -912,14 +928,30 @@ static int ov7670_enum_frameintervals(struct v4l2_subdev *sd,
|
||||||
static int ov7670_enum_framesizes(struct v4l2_subdev *sd,
|
static int ov7670_enum_framesizes(struct v4l2_subdev *sd,
|
||||||
struct v4l2_frmsizeenum *fsize)
|
struct v4l2_frmsizeenum *fsize)
|
||||||
{
|
{
|
||||||
|
struct ov7670_info *info = to_state(sd);
|
||||||
|
int i;
|
||||||
|
int num_valid = -1;
|
||||||
__u32 index = fsize->index;
|
__u32 index = fsize->index;
|
||||||
if (index >= N_WIN_SIZES)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
/*
|
||||||
fsize->discrete.width = ov7670_win_sizes[index].width;
|
* If a minimum width/height was requested, filter out the capture
|
||||||
fsize->discrete.height = ov7670_win_sizes[index].height;
|
* windows that fall outside that.
|
||||||
return 0;
|
*/
|
||||||
|
for (i = 0; i < N_WIN_SIZES; i++) {
|
||||||
|
struct ov7670_win_size *win = &ov7670_win_sizes[index];
|
||||||
|
if (info->min_width && win->width < info->min_width)
|
||||||
|
continue;
|
||||||
|
if (info->min_height && win->height < info->min_height)
|
||||||
|
continue;
|
||||||
|
if (index == ++num_valid) {
|
||||||
|
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
||||||
|
fsize->discrete.width = win->width;
|
||||||
|
fsize->discrete.height = win->height;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1417,6 +1449,47 @@ static int ov7670_g_chip_ident(struct v4l2_subdev *sd,
|
||||||
return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_OV7670, 0);
|
return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_OV7670, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ov7670_s_config(struct v4l2_subdev *sd, int dumb, void *data)
|
||||||
|
{
|
||||||
|
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||||
|
struct ov7670_config *config = data;
|
||||||
|
struct ov7670_info *info = to_state(sd);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
info->clock_speed = 30; /* default: a guess */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Must apply configuration before initializing device, because it
|
||||||
|
* selects I/O method.
|
||||||
|
*/
|
||||||
|
if (config) {
|
||||||
|
info->min_width = config->min_width;
|
||||||
|
info->min_height = config->min_height;
|
||||||
|
info->use_smbus = config->use_smbus;
|
||||||
|
|
||||||
|
if (config->clock_speed)
|
||||||
|
info->clock_speed = config->clock_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure it's an ov7670 */
|
||||||
|
ret = ov7670_detect(sd);
|
||||||
|
if (ret) {
|
||||||
|
v4l_dbg(1, debug, client,
|
||||||
|
"chip found @ 0x%x (%s) is not an ov7670 chip.\n",
|
||||||
|
client->addr << 1, client->adapter->name);
|
||||||
|
kfree(info);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
v4l_info(client, "chip found @ 0x%02x (%s)\n",
|
||||||
|
client->addr << 1, client->adapter->name);
|
||||||
|
|
||||||
|
info->fmt = &ov7670_formats[0];
|
||||||
|
info->sat = 128; /* Review this */
|
||||||
|
info->clkrc = info->clock_speed / 30;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||||
static int ov7670_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
|
static int ov7670_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
|
||||||
{
|
{
|
||||||
|
@ -1455,6 +1528,7 @@ static const struct v4l2_subdev_core_ops ov7670_core_ops = {
|
||||||
.s_ctrl = ov7670_s_ctrl,
|
.s_ctrl = ov7670_s_ctrl,
|
||||||
.queryctrl = ov7670_queryctrl,
|
.queryctrl = ov7670_queryctrl,
|
||||||
.reset = ov7670_reset,
|
.reset = ov7670_reset,
|
||||||
|
.s_config = ov7670_s_config,
|
||||||
.init = ov7670_init,
|
.init = ov7670_init,
|
||||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||||
.g_register = ov7670_g_register,
|
.g_register = ov7670_g_register,
|
||||||
|
@ -1484,7 +1558,6 @@ static int ov7670_probe(struct i2c_client *client,
|
||||||
{
|
{
|
||||||
struct v4l2_subdev *sd;
|
struct v4l2_subdev *sd;
|
||||||
struct ov7670_info *info;
|
struct ov7670_info *info;
|
||||||
int ret;
|
|
||||||
|
|
||||||
info = kzalloc(sizeof(struct ov7670_info), GFP_KERNEL);
|
info = kzalloc(sizeof(struct ov7670_info), GFP_KERNEL);
|
||||||
if (info == NULL)
|
if (info == NULL)
|
||||||
|
@ -1492,22 +1565,6 @@ static int ov7670_probe(struct i2c_client *client,
|
||||||
sd = &info->sd;
|
sd = &info->sd;
|
||||||
v4l2_i2c_subdev_init(sd, client, &ov7670_ops);
|
v4l2_i2c_subdev_init(sd, client, &ov7670_ops);
|
||||||
|
|
||||||
/* Make sure it's an ov7670 */
|
|
||||||
ret = ov7670_detect(sd);
|
|
||||||
if (ret) {
|
|
||||||
v4l_dbg(1, debug, client,
|
|
||||||
"chip found @ 0x%x (%s) is not an ov7670 chip.\n",
|
|
||||||
client->addr << 1, client->adapter->name);
|
|
||||||
kfree(info);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
v4l_info(client, "chip found @ 0x%02x (%s)\n",
|
|
||||||
client->addr << 1, client->adapter->name);
|
|
||||||
|
|
||||||
info->fmt = &ov7670_formats[0];
|
|
||||||
info->sat = 128; /* Review this */
|
|
||||||
info->clkrc = 1; /* 30fps */
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* A V4L2 driver for OmniVision OV7670 cameras.
|
||||||
|
*
|
||||||
|
* Copyright 2010 One Laptop Per Child
|
||||||
|
*
|
||||||
|
* This file may be distributed under the terms of the GNU General
|
||||||
|
* Public License, version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __OV7670_H
|
||||||
|
#define __OV7670_H
|
||||||
|
|
||||||
|
struct ov7670_config {
|
||||||
|
int min_width; /* Filter out smaller sizes */
|
||||||
|
int min_height; /* Filter out smaller sizes */
|
||||||
|
int clock_speed; /* External clock speed (MHz) */
|
||||||
|
bool use_smbus; /* Use smbus I/O instead of I2C */
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue