/***********************************************************
一:先贴摄像头应用流程,对应驱动一步步跟:
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#include <linux/fb.h>
#define CLEAR(x) memset (&(x), 0, sizeof (x))
struct buffer {
void * start;
size_t length;
};
static char * dev_name = NULL;
static int fd = -1;
struct buffer * buffers = NULL;
static unsigned int n_buffers = 0;
static int time_in_sec_capture=5;
static int fbfd = -1;
static struct fb_var_screeninfo vinfo;
static struct fb_fix_screeninfo finfo;
static char *fbp=NULL;
static long screensize=0;
static void errno_exit (const char * s)
{
fprintf (stderr, "%s error %d, %s/n",s, errno, strerror (errno));
exit (EXIT_FAILURE);
}
static int xioctl (int fd,int request,void * arg)
{
int r;
/* Here use this method to make sure cmd success*/
do r = ioctl (fd, request, arg);
while (-1 == r && EINTR == errno);
return r;
}
inline int clip(int value, int min, int max) {
return (value > max ? max : value < min ? min : value);
}
static void process_image (const void * p){
//ConvertYUVToRGB321;
unsigned char* in=(char*)p;
int width=640;
int height=480;
int istride=1280;
int x,y,j;
int y0,u,y1,v,r,g,b;
long location=0;
for ( y = 100; y < height + 100; ++y) {
for (j = 0, x=100; j < width * 2 ; j += 4,x +=2) {
location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
(y+vinfo.yoffset) * finfo.line_length;
y0 = in[j];
u = in[j + 1] - 128;
y1 = in[j + 2];
v = in[j + 3] - 128;
r = (298 * y0 + 409 * v + 128) >> 8;
g = (298 * y0 - 100 * u - 208 * v + 128) >> 8;
b = (298 * y0 + 516 * u + 128) >> 8;
fbp[ location + 0] = clip(b, 0, 255);
fbp[ location + 1] = clip(g, 0, 255);
fbp[ location + 2] = clip(r, 0, 255);
fbp[ location + 3] = 255;
r = (298 * y1 + 409 * v + 128) >> 8;
g = (298 * y1 - 100 * u - 208 * v + 128) >> 8;
b = (298 * y1 + 516 * u + 128) >> 8;
fbp[ location + 4] = clip(b, 0, 255);
fbp[ location + 5] = clip(g, 0, 255);
fbp[ location + 6] = clip(r, 0, 255);
fbp[ location + 7] = 255;
}
in +=istride;
}
}
static int read_frame (void)
{
struct v4l2_buffer buf;
unsigned int i;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
/* 11. VIDIOC_DQBUF把数据放回缓存队列*/
if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf)) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
default:
errno_exit ("VIDIOC_DQBUF");
}
}
assert (buf.index < n_buffers);
printf("v4l2_pix_format->field(%d)/n", buf.field);
//assert (buf.field ==V4L2_FIELD_NONE);
process_image (buffers[buf.index].start);
/*12. VIDIOC_QBUF把数据从缓存中读取出来*/
if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
errno_exit ("VIDIOC_QBUF");
return 1;
}
static void run (void)
{
unsigned int count;
int frames;
frames = 30 * time_in_sec_capture;
while (frames-- > 0) {
for (;;) {
fd_set fds;
struct timeval tv;
int r;
FD_ZERO (&fds);
FD_SET (fd, &fds);
tv.tv_sec = 2;
tv.tv_usec = 0;
/* 10. poll method*/
r = select (fd + 1, &fds, NULL, NULL, &tv);
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit ("select");
}
if (0 == r) {
fprintf (stderr, "select timeout/n");
exit (EXIT_FAILURE);
}
if (read_frame())
break;
}
}
}
static void stop_capturing (void)
{
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/*13. VIDIOC_STREAMOFF结束视频显示函数*/
if (-1 == xioctl (fd, VIDIOC_STREAMOFF, &type))
errno_exit ("VIDIOC_STREAMOFF");
}
static void start_capturing (void)
{
unsigned int i;
enum v4l2_buf_type type;
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
/* 8. VIDIOC_QBUF把数据从缓存中读取出来*/
if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
errno_exit ("VIDIOC_QBUF");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 9. VIDIOC_STREAMON开始视频显示函数*/
if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
errno_exit ("VIDIOC_STREAMON");
}
static void uninit_device (void)
{
unsigned int i;
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap (buffers[i].start, buffers[i].length))
errno_exit ("munmap");
if (-1 == munmap(fbp, screensize)) {
printf(" Error: framebuffer device munmap() failed./n");
exit (EXIT_FAILURE) ;
}
free (buffers);
}
static void init_mmap (void)
{
struct v4l2_requestbuffers req;
//mmap framebuffer
fbp = (char *)mmap(NULL,screensize,PROT_READ | PROT_WRITE,MAP_SHARED ,fbfd, 0);
if ((int)fbp == -1) {
printf("Error: failed to map framebuffer device to memory./n");
exit (EXIT_FAILURE) ;
}
memset(fbp, 0, screensize);
CLEAR (req);
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
/* 6. VIDIOC_REQBUFS分配内存*/
if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL == errno) {
fprintf (stderr, "%s does not support memory mapping/n", dev_name);
exit (EXIT_FAILURE);
} else {
errno_exit ("VIDIOC_REQBUFS");
}
}
if (req.count < 4) {
fprintf (stderr, "Insufficient buffer memory on %s/n",dev_name);
exit (EXIT_FAILURE);
}
buffers = calloc (req.count, sizeof (*buffers));
if (!buffers) {
fprintf (stderr, "Out of memory/n");
exit (EXIT_FAILURE);
}
for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
/* 7. VIDIOC_QUERYBUF把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址*/
if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))
errno_exit ("VIDIOC_QUERYBUF");
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
errno_exit ("mmap");
}
}
static void init_device (void)
{
struct v4l2_capability cap;
struct v4l2_cropcap cropcap;
struct v4l2_crop crop;
struct v4l2_format fmt;
unsigned int min;
// Get fixed screen information
if (-1==xioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
printf("Error reading fixed information./n");
exit (EXIT_FAILURE);
}
// Get variable screen information
if (-1==xioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
printf("Error reading variable information./n");
exit (EXIT_FAILURE);
}
screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
/* 2. VIDIOC_QUERYCAP查询驱动功能*/
if (-1 == xioctl (fd, VIDIOC_QUERYCAP, &cap)) {
if (EINVAL == errno) {
fprintf (stderr, "%s is no V4L2 device/n",dev_name);
exit (EXIT_FAILURE);
} else {
errno_exit ("VIDIOC_QUERYCAP");
}
}
/* Check if it is a video capture device*/
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
fprintf (stderr, "%s is no video capture device/n",dev_name);
exit (EXIT_FAILURE);
}
/* Check if support streaming I/O ioctls*/
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf (stderr, "%s does not support streaming i/o/n",dev_name);
exit (EXIT_FAILURE);
}
CLEAR (cropcap);
/* Set type*/
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 3. VIDIOC_CROPCAP查询驱动的修剪能力*/
/* 这里在vivi驱动中我们没有实现此方法,即不支持此操作*/
if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) {
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect;
/* 4. VIDIOC_S_CROP设置视频信号的边框*/
/* 同样不支持这个操作*/
if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) {
switch (errno) {
case EINVAL:
break;
default:
break;
}
}
}else { }
CLEAR (fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
/* 5. VIDIOC_S_FMT设置当前驱动的频捕获格式*/
if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
errno_exit ("VIDIOC_S_FMT");
init_mmap ();
}
static void close_device (void)
{
if (-1 == close (fd))
errno_exit ("close");
fd = -1;
/*14. close method*/
close(fbfd);
}
static void open_device (void)
{
struct stat st;
if (-1 == stat (dev_name, &st)) {
fprintf (stderr, "Cannot identify '%s': %d, %s/n",dev_name, errno, strerror (errno));
exit (EXIT_FAILURE);
}
if (!S_ISCHR (st.st_mode)) {
fprintf (stderr, "%s is no device/n", dev_name);
exit (EXIT_FAILURE);
}
fbfd = open("/dev/fb0", O_RDWR);
if (fbfd==-1) {
printf("Error: cannot open framebuffer device./n");
exit (EXIT_FAILURE);
}
/* 1. open the char device */
fd = open (dev_name, O_RDWR| O_NONBLOCK, 0);
if (-1 == fd) {
fprintf (stderr, "Cannot open '%s': %d, %s/n",dev_name, errno, strerror (errno));
exit (EXIT_FAILURE);
}
}
static void usage (FILE * fp,int argc,char ** argv)
{
fprintf (fp,
"Usage: %s [options]/n/n"
"Options:/n"
"-d | --device name Video device name [/dev/video]/n"
"-h | --help Print this message/n"
"-t | --how long will display in seconds/n"
"",
argv[0]);
}
static const char short_options [] = "d:ht:";
static const struct option long_options [] = {
{ "device", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ "time", no_argument, NULL, 't' },
{ 0, 0, 0, 0 }
};
int main (int argc,char ** argv)
{
dev_name = "/dev/video0";
for (;;)
{
int index;
int c;
c = getopt_long (argc, argv,short_options, long_options,&index);
if (-1 == c)
break;
switch (c) {
case 0:
break;
case 'd':
dev_name = optarg;
break;
case 'h':
usage (stdout, argc, argv);
exit (EXIT_SUCCESS);
case 't':
time_in_sec_capture = atoi(optarg);
break;
default:
usage (stderr, argc, argv);
exit (EXIT_FAILURE);
}
}
open_device();
init_device();
start_capturing();
run();
stop_capturing();
uninit_device();
close_device();
exit(EXIT_SUCCESS);
return 0;
}
/***********************************************************
二:跟ioctl对应的驱动接口,通过V4L2接口最终调用fimc控制器回调函数
***********************************************************/
\drivers\media\video\v4l2-dev.c:
static const struct file_operations v4l2_fops = {
.owner = THIS_MODULE,
.read = v4l2_read,//
.write = v4l2_write,
.open = v4l2_open,//对应应用层open函数
.get_unmapped_area = v4l2_get_unmapped_area,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,//对应drivers/media/video/v4l2-ioctl.c的video_ioctl2
#ifdef CONFIG_COMPAT
.compat_ioctl = v4l2_compat_ioctl32,
#endif
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
应用到驱动的调用流程分析:
1、
应用: fd = open (dev_name, O_RDWR| O_NONBLOCK, 0);
驱动:会调用\drivers\media\video\v4l2-dev.c的回调函数 v4l2_open。
/* Override for the open function */
static int v4l2_open(struct inode *inode, struct file *filp)
{
struct video_device *vdev;
int ret = 0;
/* Check if the video device is available */
mutex_lock(&videodev_lock);
vdev = video_devdata(filp);
/* return ENODEV if the video device has been removed
already or if it is not registered anymore. */
if (vdev == NULL || !video_is_registered(vdev)) {
mutex_unlock(&videodev_lock);
return -ENODEV;
}
/* and increase the device refcount */
video_get(vdev);
mutex_unlock(&videodev_lock);
if (vdev->fops->open)
ret = vdev->fops->open(filp);
/* decrease the refcount in case of an error */
if (ret)
video_put(vdev);
return ret;
}
然后标记处会调用此前已经通过video_register_device()注册过的video_device结构体回调函数.,这里是\drivers\media\video\samsung\fimc
struct video_device fimc_video_device[FIMC_DEVICES] = {
[0] = {
.fops = &fimc_fops,
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
},
[1] = {
.fops = &fimc_fops,
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
},
[2] = {
.fops = &fimc_fops,
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
},
};
static const struct v4l2_file_operations fimc_fops = {
.owner = THIS_MODULE,
.open = fimc_open,
.release = fimc_release,
.ioctl = video_ioctl2,
.read = fimc_read,
.write = fimc_write,
.mmap = fimc_mmap,
.poll = fimc_poll,
};
会调用fimc_open。
static int fimc_open(struct file *filp)
{
struct fimc_control *ctrl;
struct s3c_platform_fimc *pdata;
struct fimc_prv_data *prv_data;
int in_use;
int ret;
ctrl = video_get_drvdata(video_devdata(filp));
pdata = to_fimc_plat(ctrl->dev);
mutex_lock(&ctrl->lock);
in_use = atomic_read(&ctrl->in_use);
if (in_use >= FIMC_MAX_CTXS || (in_use && 1 != ctrl->id)) {
fimc_err("%s: Device busy.\n", __func__);
ret = -EBUSY;
goto resource_busy;
} else {
atomic_inc(&ctrl->in_use);
}
in_use = atomic_read(&ctrl->in_use);
prv_data = kzalloc(sizeof(struct fimc_prv_data), GFP_KERNEL);
if (!prv_data) {
fimc_err("%s: not enough memory\n", __func__);
ret = -ENOMEM;
goto kzalloc_err;
}
prv_data->ctx_id = fimc_get_free_ctx(ctrl);
if (prv_data->ctx_id < 0) {
fimc_err("%s: Context busy flag not reset.\n", __func__);
ret = -EBUSY;
goto ctx_err;
}
prv_data->ctrl = ctrl;
filp->private_data = prv_data;
if (in_use == 1) {
fimc_clk_en(ctrl, true);
if (pdata->hw_ver == 0x40)
fimc_hw_reset_camera(ctrl);//何时给摄像头发送复位信号(原理图上第4管脚,CAM_RST),因为摄像头只有接到复位信号(高电平或者低电平)才能正常工作。我们这里是在打开v4l2设备的时候,也就是open(“/dev/video0”, RD_WR)时复位的.
/* Apply things to interface register */
fimc_hwset_reset(ctrl);
if (num_registered_fb > 0) {
struct fb_info *fbinfo = registered_fb[0];
ctrl->fb.lcd_hres = (int)fbinfo->var.xres;
ctrl->fb.lcd_vres = (int)fbinfo->var.yres;
fimc_info1("%s: fd.lcd_hres=%d fd.lcd_vres=%d\n",
__func__, ctrl->fb.lcd_hres,
ctrl->fb.lcd_vres);
}
ctrl->mem.curr = ctrl->mem.base;
ctrl->status = FIMC_STREAMOFF;
if (0 != ctrl->id)
fimc_clk_en(ctrl, false);
}
//ctrl->cap->flip |= FIMC_YFLIP;
mutex_unlock(&ctrl->lock);
fimc_info1("%s opened.\n", ctrl->name);
return 0;
ctx_err:
kfree(prv_data);
kzalloc_err:
atomic_dec(&ctrl->in_use);
resource_busy:
mutex_unlock(&ctrl->lock);
return ret;
}
2.
应用:/*VIDIOC_QUERYCAP查询驱动功能*/
if (-1 == xioctl (fd, VIDIOC_QUERYCAP, &cap)) {
驱动:\drivers\media\video\v4l2-dev.c的回调函数 v4l2_ioctl
static int v4l2_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct video_device *vdev = video_devdata(filp);
if (!vdev->fops->ioctl)
return -ENOTTY;
/* Allow ioctl to continue even if the device was unregistered.
Things like dequeueing buffers might still be useful. */
return vdev->fops->ioctl(filp, cmd, arg);
}
然后会调用\drivers\media\video\samsung\fimc的video_ioctl2函数
long video_ioctl2(struct file *file,
unsigned int cmd, unsigned long arg)
{
char sbuf[128];
void *mbuf = NULL;
void *parg = (void *)arg;
long err = -EINVAL;
int is_ext_ctrl;
size_t ctrls_size = 0;
void __user *user_ptr = NULL;
#ifdef __OLD_VIDIOC_
cmd = video_fix_command(cmd);
#endif
is_ext_ctrl = (cmd == VIDIOC_S_EXT_CTRLS || cmd == VIDIOC_G_EXT_CTRLS ||
cmd == VIDIOC_TRY_EXT_CTRLS);
/* Copy arguments into temp kernel buffer */
if (_IOC_DIR(cmd) != _IOC_NONE) {
if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
parg = sbuf;
} else {
/* too big to allocate from stack */
mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
if (NULL == mbuf)
return -ENOMEM;
parg = mbuf;
}
err = -EFAULT;
if (_IOC_DIR(cmd) & _IOC_WRITE) {
unsigned long n = cmd_input_size(cmd);
if (copy_from_user(parg, (void __user *)arg, n))
goto out;
/* zero out anything we don't copy from userspace */
if (n < _IOC_SIZE(cmd))
memset((u8 *)parg + n, 0, _IOC_SIZE(cmd) - n);
} else {
/* read-only ioctl */
memset(parg, 0, _IOC_SIZE(cmd));
}
}
if (is_ext_ctrl) {
struct v4l2_ext_controls *p = parg;
/* In case of an error, tell the caller that it wasn't
a specific control that caused it. */
p->error_idx = p->count;
user_ptr = (void __user *)p->controls;
if (p->count) {
ctrls_size = sizeof(struct v4l2_ext_control) * p->count;
/* Note: v4l2_ext_controls fits in sbuf[] so mbuf is still NULL. */
mbuf = kmalloc(ctrls_size, GFP_KERNEL);
err = -ENOMEM;
if (NULL == mbuf)
goto out_ext_ctrl;
err = -EFAULT;
if (copy_from_user(mbuf, user_ptr, ctrls_size))
goto out_ext_ctrl;
p->controls = mbuf;
}
}
/* Handles IOCTL */
err = __video_do_ioctl(file, cmd, parg);
if (err == -ENOIOCTLCMD)
err = -EINVAL;
if (is_ext_ctrl) {
struct v4l2_ext_controls *p = parg;
p->controls = (void *)user_ptr;
if (p->count && err == 0 && copy_to_user(user_ptr, mbuf, ctrls_size))
err = -EFAULT;
goto out_ext_ctrl;
}
if (err < 0)
goto out;
out_ext_ctrl:
/* Copy results into user buffer */
switch (_IOC_DIR(cmd)) {
case _IOC_READ:
case (_IOC_WRITE | _IOC_READ):
if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
err = -EFAULT;
break;
}
out:
kfree(mbuf);
return err;
}
然后会调用一个大的swich case,其中会对应应用层的所有ioctl调用:
如2:xioctl (fd, VIDIOC_QUERYCAP, &cap)
3. xioctl (fd, VIDIOC_CROPCAP, &cropcap)
4. xioctl (fd, VIDIOC_S_CROP, &crop)
5. xioctl (fd, VIDIOC_S_FMT, &fmt)
6. xioctl (fd, VIDIOC_REQBUFS, &req)
7. xioctl (fd, VIDIOC_QUERYBUF, &buf)
8. xioctl (fd, VIDIOC_QBUF, &buf)
9. xioctl (fd, VIDIOC_STREAMON, &type)
11. xioctl (fd, VIDIOC_DQBUF, &buf)
12. xioctl (fd, VIDIOC_QBUF, &buf)
大都可以在switch case语句中找到对应的参数。
其中以9. VIDIOC_SREAMON为例分析。
当应用调用xioctl (fd, VIDIOC_STREAMON, &type)
驱动通过调用v4l2_dev.c的v4l2_ioctl回调函数,再到fimc_dev.c的video_ioctl2回调函数,再到v4l2_ioctl.c处调用__video_do_ioctl函数,在case找到对应的参数VIDIOC_STREAMON.
static long __video_do_ioctl(struct file *file,
unsigned int cmd, void *arg)
{
case VIDIOC_STREAMON:
{
enum v4l2_buf_type i = *(int *)arg;
if (!ops->vidioc_streamon)
break;
dbgarg(cmd, "type=%s\n", prt_names(i, v4l2_type_names));
ret = ops->vidioc_streamon(file, fh, i);
break;
}
}
标记处会调用回调函数:
const struct v4l2_ioctl_ops fimc_v4l2_ops = {
.vidioc_querycap = fimc_querycap,
.vidioc_reqbufs = fimc_reqbufs,
.vidioc_querybuf = fimc_querybuf,
.vidioc_g_ctrl = fimc_g_ctrl,
.vidioc_s_ctrl = fimc_s_ctrl,
.vidioc_s_ext_ctrls = fimc_s_ext_ctrls,
.vidioc_cropcap = fimc_cropcap,
.vidioc_g_crop = fimc_g_crop,
.vidioc_s_crop = fimc_s_crop,
.vidioc_streamon= fimc_streamon,
.vidioc_streamoff = fimc_streamoff,
.vidioc_qbuf = fimc_qbuf,
.vidioc_dqbuf = fimc_dqbuf,
.vidioc_enum_fmt_vid_cap = fimc_enum_fmt_vid_capture,
.vidioc_g_fmt_vid_cap = fimc_g_fmt_vid_capture,
.vidioc_s_fmt_vid_cap = fimc_s_fmt_vid_capture,
.vidioc_try_fmt_vid_cap = fimc_try_fmt_vid_capture,
.vidioc_enum_input = fimc_enum_input,
.vidioc_g_input = fimc_g_input,
.vidioc_s_input = fimc_s_input,
.vidioc_g_parm = fimc_g_parm,
.vidioc_s_parm = fimc_s_parm,
.vidioc_queryctrl = fimc_queryctrl,
.vidioc_querymenu = fimc_querymenu,
.vidioc_g_fmt_vid_out = fimc_g_fmt_vid_out,
.vidioc_s_fmt_vid_out = fimc_s_fmt_vid_out,
.vidioc_try_fmt_vid_out = fimc_try_fmt_vid_out,
.vidioc_g_fbuf = fimc_g_fbuf,
.vidioc_s_fbuf = fimc_s_fbuf,
.vidioc_try_fmt_vid_overlay = fimc_try_fmt_overlay,
.vidioc_g_fmt_vid_overlay = fimc_g_fmt_vid_overlay,
.vidioc_s_fmt_vid_overlay = fimc_s_fmt_vid_overlay,
};
static int fimc_streamon(struct file *filp, void *fh, enum v4l2_buf_type i)
{
struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl;
struct s3c_platform_fimc *pdata;
int ret = -1;
log_msg(LOG_DBG,"called\n");
pdata = to_fimc_plat(ctrl->dev);
if (i == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = fimc_streamon_capture(fh);
} else if (i == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
ret = fimc_streamon_output(fh);
} else {
fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and "
"V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n");
ret = -EINVAL;
}
return ret;
}
//---------------------------------------
int fimc_streamon_capture(void *fh)
{
struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl;
struct fimc_capinfo *cap = ctrl->cap;
int rot;
int ret;
fimc_dbg("%s\n", __func__);
log_msg(LOG_DBG,"called\n");
if (!ctrl->cam || !ctrl->cam->sd) {
fimc_err("%s: No capture device.\n", __func__);
return -ENODEV;
}
if (ctrl->status == FIMC_STREAMON) {
fimc_err("%s: Camera already running.\n", __func__);
return -EBUSY;
}
mutex_lock(&ctrl->v4l2_lock);
if (0 != ctrl->id)
fimc_clk_en(ctrl, true);
ctrl->status = FIMC_READY_ON;
cap->irq = 0;
fimc_hwset_enable_irq(ctrl, 0, 1);
if (!ctrl->cam->initialized)
fimc_camera_init(ctrl);
if (ctrl->id != 2 &&
ctrl->cap->fmt.colorspace != V4L2_COLORSPACE_JPEG) {
ret = fimc_camera_start(ctrl);
if (ret < 0) {
fimc_reset_capture(ctrl);
mutex_unlock(&ctrl->v4l2_lock);
return ret;
}
}
fimc_hwset_camera_type(ctrl);
fimc_hwset_camera_polarity(ctrl);
fimc_update_hwaddr(ctrl);
if (cap->fmt.pixelformat != V4L2_PIX_FMT_JPEG) {
fimc_hwset_camera_source(ctrl);
fimc_hwset_camera_offset(ctrl);
fimc_capture_scaler_info(ctrl);
fimc_hwset_prescaler(ctrl, &ctrl->sc);
fimc_hwset_scaler(ctrl, &ctrl->sc);
fimc_hwset_output_colorspace(ctrl, cap->fmt.pixelformat);
fimc_hwset_output_addr_style(ctrl, cap->fmt.pixelformat);
fimc_hwset_output_area(ctrl, cap->fmt.width, cap->fmt.height);
if (cap->fmt.pixelformat == V4L2_PIX_FMT_RGB32 ||
cap->fmt.pixelformat == V4L2_PIX_FMT_RGB565)
fimc_hwset_output_rgb(ctrl, cap->fmt.pixelformat);
else
fimc_hwset_output_yuv(ctrl, cap->fmt.pixelformat);
fimc_hwset_output_size(ctrl, cap->fmt.width, cap->fmt.height);
fimc_hwset_output_scan(ctrl, &cap->fmt);
fimc_hwset_output_rot_flip(ctrl, cap->rotate, cap->flip);
rot = fimc_mapping_rot_flip(cap->rotate, cap->flip);
if (rot & FIMC_ROT) {
fimc_hwset_org_output_size(ctrl, cap->fmt.height,
cap->fmt.width);
} else {
fimc_hwset_org_output_size(ctrl, cap->fmt.width,
cap->fmt.height);
}
fimc_hwset_jpeg_mode(ctrl, false);
} else {
fimc_hwset_output_area_size(ctrl, \
fimc_camera_get_jpeg_memsize(ctrl)/2);
fimc_hwset_jpeg_mode(ctrl, true);
}
if (ctrl->cap->fmt.colorspace == V4L2_COLORSPACE_JPEG)
fimc_hwset_scaler_bypass(ctrl);
//print_regs(ctrl);
fimc_start_capture(ctrl);
if (ctrl->cap->fmt.colorspace == V4L2_COLORSPACE_JPEG &&
ctrl->id != 2) {
struct v4l2_control cam_ctrl;
cam_ctrl.id = V4L2_CID_CAM_CAPTURE;
ret = subdev_call(ctrl, core, s_ctrl, &cam_ctrl);
if (ret < 0 && ret != -ENOIOCTLCMD) {
fimc_reset_capture(ctrl);
mutex_unlock(&ctrl->v4l2_lock);
fimc_err("%s: Error in V4L2_CID_CAM_CAPTURE\n", \
__func__);
return -EPERM;
}
}
ctrl->status = FIMC_STREAMON;
mutex_unlock(&ctrl->v4l2_lock);
return 0;
}
static int fimc_camera_init(struct fimc_control *ctrl)
{
int ret;
fimc_dbg("%s\n", __func__);
/* do nothing if already initialized */
if (ctrl->cam->initialized)
return 0;
/* enable camera power if needed */
if (ctrl->cam->cam_power)
ctrl->cam->cam_power(1);
/* subdev call for init */
ret = subdev_call(ctrl, core, init, 0);
if (ret == -ENOIOCTLCMD) {
fimc_err("%s: init subdev api not supported\n",
__func__);
return ret;
}
if (ctrl->cam->type == CAM_TYPE_MIPI) {
/* subdev call for sleep/wakeup:
* no error although no s_stream api support
*/
u32 pixelformat;
if (ctrl->cap->fmt.pixelformat == V4L2_PIX_FMT_JPEG)
pixelformat = V4L2_PIX_FMT_JPEG;
else
pixelformat = ctrl->cam->pixelformat;
subdev_call(ctrl, video, s_stream, 0);
s3c_csis_start(ctrl->cam->mipi_lanes, ctrl->cam->mipi_settle, \
ctrl->cam->mipi_align, ctrl->cam->width, \
ctrl->cam->height, pixelformat);
subdev_call(ctrl, video, s_stream, 1);
}
ctrl->cam->initialized = 1;
return 0;
}
#define subdev_call(ctrl, o, f, args...) \
v4l2_subdev_call(ctrl->cam->sd, o, f, ##args)
这里实际调用的是camera的.init回调函数。
以ov9650摄像头为例。13583432061 张晓
操作接口:
static struct v4l2_subdev_ops sensor_subdev_ops = {
.core = &sensor_subdev_core_ops,
.video = &sensor_subdev_video_ops,
};
调用接口回调函数.init初始化摄像头:
static struct v4l2_subdev_core_ops sensor_subdev_core_ops = {
.init= sensor_init,
.g_ctrl = sensor_g_control,
.s_ctrl= sensor_s_control,
.g_ext_ctrls = sensor_g_ext_controls,
.s_ext_ctrls = sensor_s_ext_controls,
.g_chip_ident = sensor_g_chip_ident,
};
/***************************************************************
分割线,以上简单介绍了camera从应用层到底层驱动的调用流程,网上参考+自己的理解,因为只是作为笔记的形式记录,难免不够认真,还有错误的地方,见谅请告知,谢谢~~
************************************************************/
/********************************以下camera驱动框架**********************/
//设备层及平台数据信息:
arch\arm\mach-s5pv210/mach-cw210.c
static void __init smdkc110_machine_init(void)
{
s3c_fimc0_set_platdata(&fimc_plat_lsi);//设置平台数据
s3c_fimc1_set_platdata(&fimc_plat_lsi);
s3c_fimc2_set_platdata(&fimc_plat_lsi);
}
static struct s3c_platform_fimc fimc_plat_lsi = {
.srclk_name = "mout_mpll",
.clk_name = "sclk_fimc",
.lclk_name = "sclk_fimc_lclk",
.clk_rate = 166750000,
.default_cam = CAMERA_PAR_A,
.camera = {
#ifdef CONFIG_CAMERA_OV3640
&ov3640,
#endif
#ifdef CONFIG_CAMERA_OV9650
&ov9650, //fimc下所包含camera
#endif
#ifdef CONFIG_CAMERA_SAA7113
&saa7113,
#endif
#ifdef CONFIG_CAMERA_TVP5150
&tvp5150,
#endif
},
.hw_ver = 0x43,
};
static struct s3c_platform_camera ov9650 = {//camera的相关平台信息
.id = CAMERA_PAR_A,
.type = CAM_TYPE_ITU,
.fmt = ITU_601_YCBCR422_8BIT,
.order422 = CAM_ORDER422_8BIT_CBYCRY,
.i2c_busnum = IIC_NUM_CAM_USED,
.info = &ov9650_i2c_info,
.pixelformat = V4L2_PIX_FMT_VYUY,
.srclk_name = "mout_mpll",
.clk_name = "sclk_cam0",
.clk_rate = 24000000,
// .line_length = 640,
.line_length = 1920,
.width = 640,
.height = 480,
.window = {
.left = 0,
.top = 0,
.width = 640,
.height= 480,
},
/* Polarity */
.inv_pclk = 0,
.inv_vsync = 0,
.inv_href = 0,
.inv_hsync = 0,
.initialized = 0,
// .cam_power = smdkv210_OV9650_power,
.cam_power = tqcam_OV9650_power,
};
void __init s3c_fimc0_set_platdata(struct s3c_platform_fimc *pd)
{
struct s3c_platform_fimc *npd;
if (!pd)
pd = &default_fimc0_data;
npd = kmemdup(pd, sizeof(struct s3c_platform_fimc), GFP_KERNEL);
if (!npd)
printk(KERN_ERR "%s: no memory for platform data\n", __func__);
else {
if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_fimc0_cfg_gpio;
if (!npd->clk_on)
npd->clk_on = s3c_fimc_clk_on;
if (!npd->clk_off)
npd->clk_off = s3c_fimc_clk_off;
npd->hw_ver = 0x45;
/* starting physical address of memory region */
npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMC0, 1);
/* size of memory region */
npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMC0, 1);
s3c_device_fimc0.dev.platform_data = npd;//把fimc的相关信息传递给平台数据
}
}
struct platform_device s3c_device_fimc0 = {
.name = "s3c-fimc",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_fimc0_resource),
.resource = s3c_fimc0_resource,
};
static struct resource s3c_fimc0_resource[] = {
[0] = {
.start = S5P_PA_FIMC0,
.end = S5P_PA_FIMC0 + S5P_SZ_FIMC0 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_FIMC0,
.end = IRQ_FIMC0,
.flags = IORESOURCE_IRQ,
},
};
//以上就把两个结构体合并到了一起,然后传递给平台数据。其中同时包含了fimc和camera(ov9650)的信息
//驱动层及相关调用流程:
//fimc的驱动架构如下:
static int fimc_register(void)
{
platform_driver_register(&fimc_driver);//注册fimc控制器驱动
return 0;
}
static struct platform_driver fimc_driver = {
.probe = fimc_probe,
.remove = fimc_remove,
.suspend = fimc_suspend,
.resume = fimc_resume,
.driver = {
.name = FIMC_NAME,
.owner = THIS_MODULE,
},
};
static int __devinit fimc_probe(struct platform_device *pdev)
{
struct s3c_platform_fimc *pdata;
struct fimc_control *ctrl;
struct clk *srclk;
int ret;
if (!fimc_dev) {
fimc_dev = kzalloc(sizeof(*fimc_dev), GFP_KERNEL);
if (!fimc_dev) {
dev_err(&pdev->dev, "%s: not enough memory\n",
__func__);
return -ENOMEM;
}
}
ctrl = fimc_register_controller(pdev);//填充fimc_control结构体
if (!ctrl) {
printk(KERN_ERR "%s: cannot register fimc\n", __func__);
goto err_alloc;
}
pdata = to_fimc_plat(&pdev->dev); //获得平台信息
if (pdata->cfg_gpio)
pdata->cfg_gpio(pdev);
/* Get fimc power domain regulator */
ctrl->regulator = regulator_get(&pdev->dev, "pd");
if (IS_ERR(ctrl->regulator)) {
fimc_err("%s: failed to get resource %s\n",
__func__, "s3c-fimc");
return PTR_ERR(ctrl->regulator);
}
/* fimc source clock */
srclk = clk_get(&pdev->dev, pdata->srclk_name);//时钟信息
if (IS_ERR(srclk)) {
fimc_err("%s: failed to get source clock of fimc\n",
__func__);
goto err_v4l2;
}
/* fimc clock */
ctrl->clk = clk_get(&pdev->dev, pdata->clk_name);
if (IS_ERR(ctrl->clk)) {
fimc_err("%s: failed to get fimc clock source\n",
__func__);
goto err_v4l2;
}
/* set parent for mclk */
clk_set_parent(ctrl->clk, srclk);
/* set rate for mclk */
clk_set_rate(ctrl->clk, pdata->clk_rate);
/* V4L2 device-subdev registration */
ret = v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);//好像只是初始化V4L2_DEVICE结构体
if (ret) {
fimc_err("%s: v4l2 device register failed\n", __func__);
goto err_fimc;
}
/* things to initialize once */
if (!fimc_dev->initialized) {
ret = fimc_init_global(pdev);
if (ret)
goto err_v4l2;
}
/* video device register */
ret = video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id);//注册基于v4l2的(video_device)设备
if (ret) {
fimc_err("%s: cannot register video driver\n", __func__);
goto err_v4l2;
}
video_set_drvdata(ctrl->vd, ctrl);
ret = device_create_file(&(pdev->dev), &dev_attr_log_level);
if (ret < 0) {
fimc_err("failed to add sysfs entries\n");
goto err_global;
}
printk(KERN_INFO "FIMC%d registered successfully\n", ctrl->id);
return 0;
err_global:
video_unregister_device(ctrl->vd);
err_v4l2:
v4l2_device_unregister(&ctrl->v4l2_dev);
err_fimc:
fimc_unregister_controller(pdev);
err_alloc:
kfree(fimc_dev);
return -EINVAL;
}
static
struct fimc_control *fimc_register_controller(struct platform_device *pdev)
{
struct s3c_platform_fimc *pdata;
struct fimc_control *ctrl;
struct resource *res;
int id, mdev_id;
id = pdev->id;
mdev_id = S5P_MDEV_FIMC0 + id;
pdata = to_fimc_plat(&pdev->dev);//得到平台数据
ctrl = get_fimc_ctrl(id);获得id号对应的fimc_control结构体指针
ctrl->id = id;
ctrl->dev = &pdev->dev;
ctrl->vd = &fimc_video_device[id];//fimc注册的主要结构体video_device
ctrl->vd->minor = id;
...
return ctrl;
}
struct video_device fimc_video_device[FIMC_DEVICES] = {
[0] = {
.fops = &fimc_fops,//两个fimc主要操作接口
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
},
[1] = {
.fops = &fimc_fops,
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
},
[2] = {
.fops = &fimc_fops,
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
},
};
static const struct v4l2_file_operations fimc_fops = {
.owner = THIS_MODULE,
.open = fimc_open,//上面已经介绍过
.release = fimc_release,
.ioctl = video_ioctl2,//同上
.read = fimc_read,
.write = fimc_write,
.mmap = fimc_mmap,
.poll = fimc_poll,
};
const struct v4l2_ioctl_ops fimc_v4l2_ops = {
.vidioc_querycap= fimc_querycap,//查询设备能力
.vidioc_reqbufs= fimc_reqbufs,//申请缓存
.vidioc_querybuf= fimc_querybuf,//将申请的缓存转换为物理地址
.vidioc_g_ctrl = fimc_g_ctrl,
.vidioc_s_ctrl = fimc_s_ctrl,
.vidioc_s_ext_ctrls = fimc_s_ext_ctrls,
.vidioc_cropcap = fimc_cropcap,
.vidioc_g_crop = fimc_g_crop,
.vidioc_s_crop = fimc_s_crop,
.vidioc_streamon= fimc_streamon,//同上,其他标记都可通过应用接口调用到这里
.vidioc_streamoff= fimc_streamoff,//结束流
.vidioc_qbuf= fimc_qbuf,//取帧
.vidioc_dqbuf= fimc_dqbuf,
.vidioc_enum_fmt_vid_cap = fimc_enum_fmt_vid_capture,
.vidioc_g_fmt_vid_cap = fimc_g_fmt_vid_capture,
.vidioc_s_fmt_vid_cap = fimc_s_fmt_vid_capture,
.vidioc_try_fmt_vid_cap = fimc_try_fmt_vid_capture,
.vidioc_enum_input = fimc_enum_input,
.vidioc_g_input = fimc_g_input,
.vidioc_s_input= fimc_s_input,
.vidioc_g_parm = fimc_g_parm,
.vidioc_s_parm = fimc_s_parm,
.vidioc_queryctrl = fimc_queryctrl,
.vidioc_querymenu = fimc_querymenu,
.vidioc_g_fmt_vid_out = fimc_g_fmt_vid_out,
.vidioc_s_fmt_vid_out = fimc_s_fmt_vid_out,
.vidioc_try_fmt_vid_out = fimc_try_fmt_vid_out,
.vidioc_g_fbuf = fimc_g_fbuf,
.vidioc_s_fbuf = fimc_s_fbuf,
.vidioc_try_fmt_vid_overlay = fimc_try_fmt_overlay,
.vidioc_g_fmt_vid_overlay = fimc_g_fmt_vid_overlay,
.vidioc_s_fmt_vid_overlay = fimc_s_fmt_vid_overlay,
}
还有一个疑问的地方,不知道中间调用环节过程,但涉及很重要的知识点,记录笔记标记下:
应用通过ioctl调用VIDIOC_S_INPUT参数,作用是查询和选则当前的视频输入。
也就是上边红色标记处。
int fimc_s_input(struct file *file, void *fh, unsigned int i)
{
struct fimc_global *fimc = get_fimc_dev();
struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl;
int ret = 0;
printk("%s: index %d\n", __func__, i);
if (i < 0 || i >= FIMC_MAXCAMS) {
fimc_err("%s: invalid input index\n", __func__);
return -EINVAL;
}
if (!fimc->camera_isvalid[i])
return -EINVAL;
if (fimc->camera[i].sd && ctrl->id != 2) {
fimc_err("%s: Camera already in use.\n", __func__);
return -EBUSY;
}
mutex_lock(&ctrl->v4l2_lock);
/* If ctrl->cam is not NULL, there is one subdev already registered.
* We need to unregister that subdev first.
*/
printk("fimc->active_camera=%d\n",fimc->active_camera);
// if(flag == -1)
if (fimc->active_camera == (-1))
{
for(i=0;i<FIMC_MAXCAMS;++i)
{
printk("detect %d cam,fimc->active_camera=%d\n",i,fimc->active_camera);
{
fimc_release_subdev(ctrl);
ctrl->cam = &fimc->camera[i];
ctrl->cam->cam_power(1);
ret = fimc_configure_subdev(ctrl);//通过fimc配置V4L2子设备
ctrl->cam->cam_power(0);
printk("%s:i=%d rtn (%d)\n",__func__,i,ret);
if (ret == 0) {
fimc->active_camera = i;
break;
}
}
}
if(i == FIMC_MAXCAMS)
{
mutex_unlock(&ctrl->v4l2_lock);
fimc_err("%s: Could not register camera sensor "
"with V4L2.\n", __func__);
return -ENODEV;
}
}
if (ctrl->id == 2) {
if (i == fimc->active_camera) {
ctrl->cam = &fimc->camera[i];
} else {
mutex_unlock(&ctrl->v4l2_lock);
return -EINVAL;
}
}
// ctrl->cap->flip |= FIMC_YFLIP;
mutex_unlock(&ctrl->v4l2_lock);
return 0;
}
static int fimc_configure_subdev(struct fimc_control *ctrl)
{
struct i2c_adapter *i2c_adap;
struct i2c_board_info *i2c_info;
struct v4l2_subdev *sd;
unsigned short addr;
char *name;
/* set parent for mclk */
if (clk_get_parent(ctrl->cam->clk->parent))
clk_set_parent(ctrl->cam->clk->parent, ctrl->cam->srclk);
/* set rate for mclk */
if (clk_get_rate(ctrl->cam->clk))
clk_set_rate(ctrl->cam->clk, ctrl->cam->clk_rate);
i2c_adap = i2c_get_adapter(ctrl->cam->i2c_busnum);
if (!i2c_adap)
fimc_err("subdev i2c_adapter missing-skip registration\n");
i2c_info = ctrl->cam->info;
if (!i2c_info) {
fimc_err("%s: subdev i2c board info missing\n", __func__);
return -ENODEV;
}
name = i2c_info->type;
if (!name) {
fimc_err("subdev i2c driver name missing-skip registration\n");
return -ENODEV;
}
addr = i2c_info->addr;
if (!addr) {
fimc_err("subdev i2c address missing-skip registration\n");
return -ENODEV;
}
/*
* NOTE: first time subdev being registered,
* s_config is called and try to initialize subdev device
* but in this point, we are not giving MCLK and power to subdev
* so nothing happens but pass platform data through
*/
sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,
name, i2c_info, &addr);//将camera同时注册为i2c和v4l2设备
if (!sd) {
fimc_err("%s: v4l2 subdev board registering failed\n",
__func__);
}
/* Assign subdev to proper camera device pointer */
ctrl->cam->sd = sd;
if(sd == NULL)
return -1;
return 0;
}
/* Load an i2c sub-device. */
struct v4l2_subdev *v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev,
struct i2c_adapter *adapter, const char *module_name,
struct i2c_board_info *info, const unsigned short *probe_addrs)
{
struct v4l2_subdev *sd = NULL;
struct i2c_client *client;
BUG_ON(!v4l2_dev);
if (module_name)
request_module(module_name);
/* Create the i2c client */
if (info->addr == 0 && probe_addrs)
client = i2c_new_probed_device(adapter, info, probe_addrs);
else
client = i2c_new_device(adapter, info);//添加client设备到i2c总线
/* Note: by loading the module first we are certain that c->driver
will be set if the driver was found. If the module was not loaded
first, then the i2c core tries to delay-load the module for us,
and then c->driver is still NULL until the module is finally
loaded. This delay-load mechanism doesn't work if other drivers
want to use the i2c device, so explicitly loading the module
is the best alternative. */
if (client == NULL || client->driver == NULL)
goto error;
/* Lock the module so we can safely get the v4l2_subdev pointer */
if (!try_module_get(client->driver->driver.owner))
goto error;
sd = i2c_get_clientdata(client);
/* Register with the v4l2_device which increases the module's
use count as well. */
if (v4l2_device_register_subdev(v4l2_dev, sd))//注册v4l2设备
sd = NULL;
/* Decrease the module use count to match the first try_module_get. */
module_put(client->driver->driver.owner);
if (sd) {
/* We return errors from v4l2_subdev_call only if we have the
callback as the .s_config is not mandatory */
int err = v4l2_subdev_call(sd, core, s_config,//此处会调用到camera的.s_config回调函数
info->irq, info->platform_data);
if (err && err != -ENOIOCTLCMD) {
v4l2_device_unregister_subdev(sd);
sd = NULL;
}
}
error:
/* If we have a client but no subdev, then something went wrong and
we must unregister the client. */
if (client && sd == NULL)
i2c_unregister_device(client);
return sd;
}
//camera mt9v011 driver:
include/media/ v4l2-i2c-drv.h://此处驱动注册时通过引用头文件的方式,跟平常的驱动注册一个道理
module_init(v4l2_i2c_drv_init);
static int __init v4l2_i2c_drv_init(void)
60 {
61 v4l2_i2c_driver.driver.name = v4l2_i2c_data.name;
62 v4l2_i2c_driver.command = v4l2_i2c_data.command;
63 v4l2_i2c_driver.probe = v4l2_i2c_data.probe;
64 v4l2_i2c_driver.remove = v4l2_i2c_data.remove;
65 v4l2_i2c_driver.suspend = v4l2_i2c_data.suspend;
66 v4l2_i2c_driver.resume = v4l2_i2c_data.resume;
67 v4l2_i2c_driver.id_table = v4l2_i2c_data.id_table;
68 return i2c_add_driver(&v4l2_i2c_driver);
69 }
//
static struct v4l2_i2c_driver_data v4l2_i2c_data = {
.name = "mt9v011",
.probe = mt9v011_probe,
.remove = mt9v011_remove,
.id_table = mt9v011_id,
};
/****************************************************************************
I2C Client & Driver
****************************************************************************/
static int mt9v011_probe(struct i2c_client *c,
const struct i2c_device_id *id)
{
u16 version;
struct mt9v011 *core;
struct v4l2_subdev *sd;
/* Check if the adapter supports the needed features */
if (!i2c_check_functionality(c->adapter,
I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
return -EIO;
core = kzalloc(sizeof(struct mt9v011), GFP_KERNEL);
if (!core)
return -ENOMEM;
sd = &core->sd;
v4l2_i2c_subdev_init(sd, c, &mt9v011_ops);//使v4l2子设备和i2c client关联
/* Check if the sensor is really a MT9V011 */
version = mt9v011_read(sd, R00_MT9V011_CHIP_VERSION);
if ((version != MT9V011_VERSION) &&
(version != MT9V011_REV_B_VERSION)) {
v4l2_info(sd, "*** unknown micron chip detected (0x%04x).\n",
version);
kfree(core);
return -EINVAL;
}
core->global_gain = 0x0024;
core->width = 640;
core->height = 480;
core->xtal = 27000000; /* Hz */
v4l_info(c, "chip found @ 0x%02x (%s - chip version 0x%04x)\n",
c->addr << 1, c->adapter->name, version);
return 0;
}