linux下的cmos驱动设计分析:
cmos摄像头驱动设计主要分为几部分:1.ARM处理器摄像头接口部分,2.通过i2c接口控制的摄像头芯片部分,被称为senor部分,3.就是上述两部分直接的联系部分,写在板级文件mach-boardname.c文件中。
其实这个摄像头驱动不是太难,摄像头驱动设计的目标就是,ARM处理器控制摄像头芯片,使其能够采集图像然后处理器把图像读出来。
围绕着这个目的,linux系统提供了v4l2架构,架构的目的是让我们程序员开发的时候更加方便,有个标准的规范,我们程序员在开发驱动的时候,就按着它给的标准的规范,把我们对硬件的操作写到框架中,这样在使用标准的应用程序操作这个设备的时候,,,就通过一层一层,内核中框架的调用对硬件进行了操作。
对于摄像头驱动来说,
我就写一写驱动的注册过程和应用程序对应的驱动程序中的操作吧
驱动的注册:
在我现在看的linux 3.0.12内核源码中,注册的关键代码主要在drivers/media/video/samsung/fimc/fimc_dev.c 文件的fimc_probe(struct platform_device *pdev)函数中
在这个函数中,我看关键部分代码主要是以下几个函数
1 . ctrl = fimc_register_controller(pdev);
2. ret = v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);
3.ret = fimc_init_global(pdev);
4.ret = video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id);
接下来,一一分析这几个函数,我想,注册过程就大体知道了。
1.ctrl = fimc_register_controller(pdev);
ctrl是fimc_probe刚开始定义的一个结构体指针struct fimc_control *ctrl;
形参pdev是什么呢?是fimc的平台设备struct platform_device *pdev,这个在哪定义?
struct platform_device s3c_device_fimc0 = {
.name = "s3c-fimc",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_fimc0_resource),
.resource = s3c_fimc0_resource,
};
还有一个很重要的函数就是在板级配置文件smdk4x12_machine_init函数中,s3c_fimc0_set_platdata(&fimc_plat);这句话的作用为
s3c_device_fimc0.dev.platform_data=fimc_plat;
fimc_plat为什么样的结构体呢?其定义为
static struct s3c_platform_fimc fimc_plat = {
.default_cam = CAMERA_PAR_A,
.camera = &ov5640,
.hw_ver = 0x51,
} 它的作用主要是描述了fimc结构的设置,比如读取哪种格式的数据,读取时采用哪种形式的时序,fimc读取时的时钟频率等,
其中ov5640这个结构展开为
static struct s3c_platform_camera ov5640 = {
.id = CAMERA_PAR_A,
.clk_name = "sclk_cam0",
.i2c_busnum = 7,
.cam_power = smdk4x12_cam1_reset,
.type = CAM_TYPE_ITU,
.fmt = ITU_601_YCBCR422_8BIT,
.order422 = CAM_ORDER422_8BIT_CBYCRY,
.info = &ov5640_i2c_info,
.pixelformat = V4L2_PIX_FMT_UYVY, //modify by cym V4L2_PIX_FMT_UYVY,
.srclk_name = "xusbxti",
.clk_rate = 24000000,
.line_length = 1920,
.width = 640,
.height = 480,
.window = {
.left = 0,
.top = 0,
.width = 640,
.height = 480,
},
/* Polarity */
.inv_pclk = 0,
.inv_vsync = 1,
.inv_href = 0,
.inv_hsync = 0,
.reset_camera = 1,
.initialized = 0,
.layout_rotate = 0 //for shuping, //180,
};
上面是为我们分析函数进行了准备,把会用到的相关的结构体都进行了罗列,下面开始分析 ctrl = fimc_register_controller(pdev);
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, err;
struct cma_info mem_info;
struct clk *sclk_fimc_lclk = NULL;
struct clk *fimc_src_clk = NULL;
id = pdev->id;
pdata = to_fimc_plat(&pdev->dev);
ctrl = get_fimc_ctrl(id);
ctrl->id = id;
ctrl->dev = &pdev->dev;
ctrl->vd = &fimc_video_device[id];
ctrl->vd->minor = id;
ctrl->log = FIMC_LOG_DEFAULT;
ctrl->power_status = FIMC_POWER_OFF;
/* CMA */
sprintf(ctrl->cma_name, "%s%d", FIMC_CMA_NAME, ctrl->id);
err = cma_info(&mem_info, ctrl->dev, 0);
fimc_info1("%s : [cma_info] start_addr : 0x%x, end_addr : 0x%x, "
"total_size : 0x%x, free_size : 0x%x\n",
__func__, mem_info.lower_bound, mem_info.upper_bound,
mem_info.total_size, mem_info.free_size);
if (err) {
fimc_err("%s: get cma info failed\n", __func__);
ctrl->mem.size = 0;
ctrl->mem.base = 0;
} else {
ctrl->mem.size = mem_info.total_size;
ctrl->mem.base = (dma_addr_t)cma_alloc
(ctrl->dev, ctrl->cma_name, (size_t)ctrl->mem.size, 0);
}
printk(KERN_DEBUG "ctrl->mem.size = 0x%x\n", ctrl->mem.size);
printk(KERN_DEBUG "ctrl->mem.base = 0x%x\n", ctrl->mem.base);
ctrl->mem.curr = ctrl->mem.base;
ctrl->status = FIMC_STREAMOFF;
switch (pdata->hw_ver) {
case 0x40:
ctrl->limit = &fimc40_limits[id];
break;
case 0x43:
case 0x45:
ctrl->limit = &fimc43_limits[id];
break;
case 0x50:
ctrl->limit = &fimc50_limits[id];
break;
case 0x51:
ctrl->limit = &fimc51_limits[id];
break;
default:
ctrl->limit = &fimc51_limits[id];
fimc_err("%s: failed to get HW version\n", __func__);
break;
}
sprintf(ctrl->name, "%s%d", FIMC_NAME, id);
strcpy(ctrl->vd->name, ctrl->name);
atomic_set(&ctrl->in_use, 0);
mutex_init(&ctrl->lock);
mutex_init(&ctrl->v4l2_lock);
spin_lock_init(&ctrl->outq_lock);
init_waitqueue_head(&ctrl->wq);
/* get resource for io memory */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
fimc_err("%s: failed to get io memory region\n", __func__);
return NULL;
}
/* request mem region */
res = request_mem_region(res->start, res->end - res->start + 1,
pdev->name);
if (!res) {
fimc_err("%s: failed to request io memory region\n", __func__);
return NULL;
}
/* ioremap for register block */
ctrl->regs = ioremap(res->start, res->end - res->start + 1);
if (!ctrl->regs) {
fimc_err("%s: failed to remap io region\n", __func__);
return NULL;
}
/* irq */
ctrl->irq = platform_get_irq(pdev, 0);
if (request_irq(ctrl->irq, fimc_irq, IRQF_DISABLED, ctrl->name, ctrl))
fimc_err("%s: request_irq failed\n", __func__);
if (soc_is_exynos4210())
fimc_src_clk = clk_get(&pdev->dev, "mout_mpll");
else
fimc_src_clk = clk_get(&pdev->dev, "mout_mpll_user");
if (IS_ERR(fimc_src_clk)) {
dev_err(&pdev->dev, "failed to get parent clock\n");
iounmap(ctrl->regs);
return NULL;
}
sclk_fimc_lclk = clk_get(&pdev->dev, FIMC_CORE_CLK);
if (IS_ERR(sclk_fimc_lclk)) {
dev_err(&pdev->dev, "failed to get sclk_fimc_lclk\n");
iounmap(ctrl->regs);
clk_put(fimc_src_clk);
return NULL;
}
if (clk_set_parent(sclk_fimc_lclk, fimc_src_clk)) {
dev_err(&pdev->dev, "unable to set parent %s of clock %s.\n",
fimc_src_clk->name, sclk_fimc_lclk->name);
iounmap(ctrl->regs);
clk_put(sclk_fimc_lclk);
clk_put(fimc_src_clk);
return NULL;
}
clk_set_rate(sclk_fimc_lclk, FIMC_CLK_RATE);
clk_put(sclk_fimc_lclk);
clk_put(fimc_src_clk);
#if (!defined(CONFIG_EXYNOS_DEV_PD) || !defined(CONFIG_PM_RUNTIME))
fimc_hwset_reset(ctrl);
#endif
return ctrl;
}
展开发现,上面代码主要做的了:
1,填充了struct fimc_control *ctrl;这个结构,这个结构是用来描述fimc控制器的,
2,映射了FIMC的的寄存器
3.申请设置了FImc控制器的时钟
*****************************************
接下来分析第二个函数 ret = v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);
这里的形参&pdev->dev,&ctrl,还是我们开始分析时提到的那些结构,
展开函数
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
if (v4l2_dev == NULL)
return -EINVAL;
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
mutex_init(&v4l2_dev->ioctl_lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
v4l2_dev->dev = dev;
if (dev == NULL) {
/* If dev == NULL, then name must be filled in by the caller */
WARN_ON(!v4l2_dev->name[0]);
return 0;
}
/* Set name to driver name + device name if it is empty. */
if (!v4l2_dev->name[0])
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
dev->driver->name, dev_name(dev));
if (!dev_get_drvdata(dev))
dev_set_drvdata(dev, v4l2_dev);
return 0;
}
这个函数主要干了几件事:
2.稍微对&v4l2_dev进行了设置
3.v4l2_dev->dev = dev;把形参平台设备结构中的dev关联到v4l2_dev->dev,以后有用
4.if (!dev_get_drvdata(dev)) dev_set_drvdata(dev, v4l2_dev); 使得 平台设备结构中的dev和这个v4l2_dev,实现相互关联
接下来看第3个关键函数,ret = fimc_init_global(pdev);
static int fimc_init_global(struct platform_device *pdev)
{
struct fimc_control *ctrl;
struct s3c_platform_fimc *pdata;
struct s3c_platform_camera *cam;
struct clk *srclk;
int id, i;
pdata = to_fimc_plat(&pdev->dev);
id = pdev->id;
ctrl = get_fimc_ctrl(id);
/* Registering external camera modules. re-arrange order to be sure */
for (i = 0; i < FIMC_MAXCAMS; i++) {
cam = pdata->camera[i];
if (!cam)
break;
/* WriteBack doesn't need clock setting */
if ((cam->id == CAMERA_WB) || (cam->id == CAMERA_WB_B)) {
fimc_dev->camera[i] = cam;
fimc_dev->camera_isvalid[i] = 1;
fimc_dev->camera[i]->initialized = 0;
continue;
}
/* source clk for MCLK*/
srclk = clk_get(&pdev->dev, cam->srclk_name);
if (IS_ERR(srclk)) {
fimc_err("%s: failed to get srclk source\n", __func__);
return -EINVAL;
}
/* mclk */
cam->clk = clk_get(&pdev->dev, cam->clk_name);
if (IS_ERR(cam->clk)) {
fimc_err("%s: failed to get mclk source\n", __func__);
return -EINVAL;
}
if (clk_set_parent(cam->clk, srclk)) {
dev_err(&pdev->dev, "unable to set parent %s of clock %s.\n",
srclk->name, cam->clk->name);
clk_put(srclk);
clk_put(cam->clk);
return -EINVAL;
}
/* Assign camera device to fimc */
fimc_dev->camera[i] = cam;
fimc_dev->camera_isvalid[i] = 1;
fimc_dev->camera[i]->initialized = 0;
}
fimc_dev->mclk_status = CAM_MCLK_OFF;
fimc_dev->active_camera = -1;
fimc_dev->initialized = 1;
return 0;
}
根据平台设备设置的信息,初始化fimc_dev这个结构体,这是一个全局变量,在fimc_dev.c中有定义,struct fimc_global *fimc_dev;
/**
* struct fimc_dev - abstraction for FIMC entity
* @slock: the spinlock protecting this data structure
* @lock: the mutex protecting this data structure
* @pdev: pointer to the FIMC platform device
* @pdata: pointer to the device platform data
* @variant: the IP variant information
* @id: FIMC device index (0..FIMC_MAX_DEVS)
* @num_clocks: the number of clocks managed by this device instance
* @clock: clocks required for FIMC operation
* @regs: the mapped hardware registers
* @regs_res: the resource claimed for IO registers
* @irq: FIMC interrupt number
* @irq_queue: interrupt handler waitqueue
* @m2m: memory-to-memory V4L2 device information
* @vid_cap: camera capture device information
* @state: flags used to synchronize m2m and capture mode operation
* @alloc_ctx: videobuf2 memory allocator context
*/
struct fimc_dev {
spinlock_t slock;
struct mutex lock;
struct platform_device *pdev;
struct s5p_platform_fimc *pdata;
struct samsung_fimc_variant *variant;
u16 id;
u16 num_clocks;
struct clk *clock[MAX_FIMC_CLOCKS];
void __iomem *regs;
struct resource *regs_res;
int irq;
wait_queue_head_t irq_queue;
struct work_struct work_struct;
struct workqueue_struct *irq_workqueue;
struct fimc_m2m_device m2m;
struct fimc_vid_cap vid_cap;
unsigned long state;
struct vb2_alloc_ctx *alloc_ctx;
struct fimc_addr paddr[FIMC_MAX_OUT_BUFS];
#ifdef CONFIG_VIDEOBUF2_SDVMM
enum vcm_dev_id vcm_id;
#endif
const struct fimc_vb2 *vb2;
};
形参ctrl->vd为上面介绍的第一个函数中赋值的,为ctrl->vd = &fimc_video_device[id];ctrl->id代码是FIMC几,0,1,2或者3
/* Register video devices. Note that if video_register_device fails,
the release() callback of the video_device structure is *not* called, so
the caller is responsible for freeing any data. Usually that means that
you call video_device_release() on failure. */
static inline int __must_check video_register_device(struct video_device *vdev,
int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}
注册video devices ,
展开看看
/**
* __video_register_device - register video4linux devices
* @vdev: video device structure we want to register
* @type: type of device to register
* @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ...
* -1 == first free)
* @warn_if_nr_in_use: warn if the desired device node number
* was already in use and another number was chosen instead.
* @owner: module that owns the video device node
*
* The registration code assigns minor numbers and device node numbers
* based on the requested type and registers the new device node with
* the kernel.
*
* This function assumes that struct video_device was zeroed when it
* was allocated and does not contain any stale date.
*
* An error is returned if no free minor or device node number could be
* found, or if the registration of the device node failed.
*
* Zero is returned on success.
*
* Valid types are
*
* %VFL_TYPE_GRABBER - A frame grabber
*
* %VFL_TYPE_VBI - Vertical blank data (undecoded)
*
* %VFL_TYPE_RADIO - A radio card
*
* %VFL_TYPE_SUBDEV - A subdevice
*/
int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
int i = 0;
int ret;
int minor_offset = 0;
int minor_cnt = VIDEO_NUM_DEVICES;
const char *name_base;
/* A minor value of -1 marks this video device as never
having been registered */
vdev->minor = -1;
/* the release callback MUST be present */
WARN_ON(!vdev->release);
if (!vdev->release)
return -EINVAL;
/* v4l2_fh support */
spin_lock_init(&vdev->fh_lock);
INIT_LIST_HEAD(&vdev->fh_list);
/* Part 1: check device type */
switch (type) {
case VFL_TYPE_GRABBER:
name_base = "video";
break;
case VFL_TYPE_VBI:
name_base = "vbi";
break;
case VFL_TYPE_RADIO:
name_base = "radio";
break;
case VFL_TYPE_SUBDEV:
name_base = "v4l-subdev";
break;
default:
printk(KERN_ERR "%s called with unknown type: %d\n",
__func__, type);
return -EINVAL;
}
vdev->vfl_type = type;
vdev->cdev = NULL;
if (vdev->v4l2_dev) {
if (vdev->v4l2_dev->dev)
vdev->parent = vdev->v4l2_dev->dev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
/* If the prio state pointer is NULL, then use the v4l2_device
prio state. */
if (vdev->prio == NULL)
vdev->prio = &vdev->v4l2_dev->prio;
}
/* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* Keep the ranges for the first four types for historical
* reasons.
* Newer devices (not yet in place) should use the range
* of 128-191 and just pick the first free minor there
* (new style). */
switch (type) {
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO:
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI:
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128;
minor_cnt = 64;
break;
}
#endif
/* Pick a device node number */
mutex_lock(&videodev_lock);
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
if (nr == minor_cnt) {
printk(KERN_ERR "could not get a free device node number\n");
mutex_unlock(&videodev_lock);
return -ENFILE;
}
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* 1-on-1 mapping of device node number to minor number */
i = nr;
#else
/* The device node number and minor numbers are independent, so
we just find the first free minor number. */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)
break;
if (i == VIDEO_NUM_DEVICES) {
mutex_unlock(&videodev_lock);
printk(KERN_ERR "could not get a free minor\n");
return -ENFILE;
}
#endif
vdev->minor = i + minor_offset;
vdev->num = nr;
devnode_set(vdev);
/* Should not happen since we thought this minor was free */
WARN_ON(video_device[vdev->minor] != NULL);
vdev->index = get_index(vdev);
mutex_unlock(&videodev_lock);
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc();
if (vdev->cdev == NULL) {
ret = -ENOMEM;
goto cleanup;
}
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
if (ret < 0) {
printk(KERN_ERR "%s: cdev_add failed\n", __func__);
kfree(vdev->cdev);
vdev->cdev = NULL;
goto cleanup;
}
/* Part 4: register the device with sysfs */
vdev->dev.class = &video_class;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
if (vdev->parent)
vdev->dev.parent = vdev->parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
if (ret < 0) {
printk(KERN_ERR "%s: device_register failed\n", __func__);
goto cleanup;
}
/* Register the release callback that will be called when the last
reference to the device goes away. */
vdev->dev.release = v4l2_device_release;
if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,
name_base, nr, video_device_node_name(vdev));
/* Increase v4l2_device refcount */
if (vdev->v4l2_dev)
v4l2_device_get(vdev->v4l2_dev);
#if defined(CONFIG_MEDIA_CONTROLLER)
/* Part 5: Register the entity. */
if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
vdev->vfl_type != VFL_TYPE_SUBDEV) {
vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
vdev->entity.name = vdev->name;
vdev->entity.v4l.major = VIDEO_MAJOR;
vdev->entity.v4l.minor = vdev->minor;
ret = media_device_register_entity(vdev->v4l2_dev->mdev,
&vdev->entity);
if (ret < 0)
printk(KERN_WARNING
"%s: media_device_register_entity failed\n",
__func__);
}
#endif
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
mutex_lock(&videodev_lock);
video_device[vdev->minor] = vdev;
mutex_unlock(&videodev_lock);
return 0;
cleanup:
mutex_lock(&videodev_lock);
if (vdev->cdev)
cdev_del(vdev->cdev);
devnode_clear(vdev);
mutex_unlock(&videodev_lock);
/* Mark this video device as never having been registered. */
vdev->minor = -1;
return ret;
}
1.确定要注册的设备类型,与传入的那个形参有关,一般为video类型
2.查找相同类型设备的设备节点号,比如这次注册了VIDEO0,那下次执行这个函数时,要检查出来,0这个号已经被注册,注册的就是VIDEO1
3.注册设备,注册的是一个字符设备,所以某种意思上说,video设备也属于一个字符设备