我们解析一下MINI2440开发板的 CMOS摄像头的驱动。该驱动总共
包括以下文件:sccb.c、sccb.h、s3c2440_ov9650.c、s3c2440camif.h、
s3c2440camif.c五个文件!
s3c2440camif.c用于从 cmos接口获取图像数据和将数据传输到进程
空间(在有 app读取时)。
s3c2440_ov9650.c 读取和配置 ov9650寄存器。通过 iic接口传输数据。
设备地址是 60(#define OV9650_SCCB_ADDR 0x60).比如进行初
始化和 product id获取.
sccb.c 定义了去读 ov9650的寄存器的具体方法,是时序模拟的 iic。
而 s3c2440_ov9650.c里是调用这些具体方法去读写 ov9650的寄存器
的。
所以我们先
sccb.c!
首先看看它的结构:
可以看到该程序是处理读写数据到 ov9650的过程。
首先我们应该知道 OV9650 内部有大量的寄存器需要配置,这就需
要另外的数据接口。 OV9650 的数据接口称为 SCCB(串行摄像控制
总线),它由两条数据线组成:一个是用于传输时钟信号的 SIO_C,
另一个是用于传输数据信号的 SIO_D。SCCB 的传输
与 IIC 的
极其相似,只不过 II C在每传输完一个字节后,接收数据的一方要发
送一位的确认数据,而 SCCB一次要传输 9位数据,前 8位为有用数
据,而第 9位数据在写周期中是 Don’t-Care 位(即不必关心位),在
读周期中是 NA位。SCCB 定义数据传输的基本单元为相(phase),
即一个相传输一个字节数据。SCCB只包括三种传输周期,即 3相写
传输周期(三个相依次为设备从地址,内存地址,所写数据),2相
写传输周期(两个相依次为设备从地址,内存地址)和 2相读传输周
期(两个相依次为设备从地址,所读数据)。当需要写操作时,应用
3相写传输周期,当需要读操作时,依次应用 2相写传输周期和 2相
读传输周期。 因此 SCCB 一次只能读或写一个字节。下面我们就用
s3c244的 IIC总线接口分别与 OV9650的 SIO_C和 SIO_D相连接来
实现 SCCB的功能。
接下来对一些重要的函数讲一讲。
static void __inline__ sccb_start(void)
{
CFG_WRITE(SIO_D);
Low(SIO_D);
WAIT_STABLE(); //延时一会,保持一定的低电平,以此代
sccb
总线开始传输数据
这个函数原型是:
#define CFG_WRITE(x)
do{s3c2410_gpio_cfgpin(x,S3
C2410_GPIO_OUTPUT);smp
_mb();}while(0)。通过这个宏
定义可以看出它是配置
GPE14为输出管脚。
批注[A1]:
这个函数的原型
是:#define Low(x)
do{s3c2410_gpio_setpin(x,0)
; smp_mb();}while(0)
可见是输出低电平的功能!
批注[A2]:
}
值得提出的是内核在对管脚的寄存器配置中用到了一个概念:内存屏
障!即 smp_mb()函数。
什么叫内存屏障?下面给出例子:
#define set_mb(var, value) do { var = value; mb(); } while (0)
#define mb() __asm__ __volatile__ ("" : : : "memory")
1 ) set_mb(),mb(),barrier() 函 数 追 踪 到 底 , 就 是 __asm__
__volatile__("":::"memory"),而这行代码就是内存屏障。
2)__asm__用于指示编译器在此插入汇编语句
3)__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语
句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
4)memory 强制 gcc编译器假设 RAM所有内存单元均被汇编指令修
改,这样 cpu中的 registers和 cache中已缓存的内存单元中的数据将
作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止
了 cpu又将 registers,cache中的数据用于去优化指令,而避免去访问
内存。
5)"":::表示这是个空指令。barrier()不用在此插入一条串行化汇编指
令。在后文将讨论什么叫串行化指令。
6)__asm__,__volatile__,memory在前面已经解释
在 linux/include/asm-i386/system.h定义:
#define mb() __asm__ __volatile__ ("lock; addl
$0,0(%%esp)": : :"memory")
7)lock前缀表示将后面这句汇编语句:"addl $0,0(%%esp)"作为 cpu
的一个内存屏障。
8)addl $0,0(%%esp)表示将数值 0加到 esp寄存器中,而该寄存器指
向栈顶的内存单元。加上一个 0,esp寄存器的数值依然不变。即这
是一条无用的汇编指令。在此利用这条无价值的汇编指令来配合 lock
指令,在__asm__,__volatile__,memory的作用下,用作 cpu的内存屏
障。
9)set_task_state()带有一个 memory barrier,set_task_state()肯定是安
全的,但 __set_task_state()可能会快些。
关于 barrier()宏实际上也是优化屏障:
#define barrier() __asm__ __volatile__("": : :"memory")
CPU越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句
实际上不生成任何代码,但可使 gcc在 barrier()之后刷新寄存器对变
量的分配。
不论是 gcc编译器的优化还是处理器本身采用的大量优化,如Write
buffer, Lock-up free, Non- blocking reading, Register allocation,
Dynamic scheduling, Multiple issues 等,都可能使得实际执行可能违
反程序顺序,因此,引入内存屏障来保证事件的执行次序严格按程序
顺序来执行。
使用内存屏障强加的严格的 CPU内存事件次序,保证程序的执行看
上去象是遵循顺序一致性模型。
接下去的函数是写一个字节到芯片中:
static void __inline__ sccb_write_byte(u8 data)
{
int i;
CFG_WRITE(SIO_D);
WAIT_STABLE();
/* write 8-bits octet. */
for (i=0;i<8;i++)
{
Low(SIO_C);
WAIT_STABLE();
if (data & 0x80)
{
High(SIO_D);
}
else
{
Low(SIO_D);
}
data = data<<1;
首先置数据总线为
低电平!并等待一会儿!
批注[A3]:
然后开始传输一个
字节。很据时序,首先置时
钟为低电平!然后等待一会!
批注[A4]:
并行数据转串行数
据,我们先传输最高位!将
我们要传输的字节与 0x80位
与,没位与一次,该字节就
左移一次,以保证每位都与
0x80都位与过,以得到整个
数据传输图!通过程序可以
看出字节是在时钟低电平时
变化的!这与 IIC总线协议一
致!
批注[A5]:
WAIT_CYCLE();
High(SIO_C);
WAIT_CYCLE();
}
/* write byte done, wait the Don't care bit now. */
{
Low(SIO_C);
High(SIO_D);
CFG_READ(SIO_D);
WAIT_CYCLE();
High(SIO_C);
WAIT_CYCLE();
}
}
之后读一个字节函数与此类似!记住这里是串行数据转并行,同样高
位在前!这里的读写都是以 ov9650芯片为参考物的!
Sccb总线的 stop函数与开始类似!
有了上面四个基本函数,我们就可以根据 sccb总线的协议来定义读
然后将时钟信号置
高!这样循环下去直到 8位
有效数据都传输完!
批注[A6]:
写函数了。如下:(记住读写周期不一样的)
void sccb_write(u8 IdAddr, u8 SubAddr, u8 data)
{
down(&bus_lock);
sccb_start();
sccb_write_byte(IdAddr);
sccb_write_byte(SubAddr);
sccb_write_byte(data);
sccb_stop();
up (&bus_lock);
}
u8 sccb_read(u8 IdAddr, u8 SubAddr)
{
u8 data;
down(&bus_lock);
sccb_start();
sccb_write_byte(IdAddr);
sccb_write_byte(SubAddr);
sccb_stop();
因为我们在这里要
操作寄存器,所以在操作前
都应该先给其上锁!避免操
作过程中芯片的寄存器被修
改!
批注[A7]:
写函数只需要三
相,即设备从地址,内存地
址,要写的数据!
批注[A8]:
读操作则首先是两
相在三相:写设备从地址,
内存地址,然后设备从地址、
读的数据!
批注[A9]:
sccb_start();
sccb_write_byte(IdAddr|0x01);
data = sccb_read_byte();
sccb_stop();
up(&bus_lock);
return data;
}
至于 sccb总线的初始化与清除函数就不多讲了!
接着我们来分析 s3c2440_ov9650.c!
同样看其结构图:
首先有一个结构体:
static struct ov9650_reg
{
u8 subaddr;
u8 value;
}regs[] = {。。。。。。。。。}
该结构体是配置 ov9650芯片寄存器的。这个是厂家提供的,我们无
需多管,只要知道它的变量是子地址与对应的值!
然后有打开与关闭 0v9650芯片的函数:
static void __inline__ ov9650_poweron(void)
{
s3c2410_gpio_cfgpin(S3C2410_GPG(12),
S3C2410_GPIO_OUTPUT);
s3c2410_gpio_setpin(S3C2410_GPG(12), 0);
mdelay(20);
}
static void __inline__ ov9650_poweroff(void)
{
s3c2410_gpio_cfgpin(S3C2410_GPG(12),
S3C2410_GPIO_OUTPUT);
s3c2410_gpio_setpin(S3C2410_GPG(12), 1);
mdelay(20);
}
然后就用到我们之前定义的 sccb总线的函数了:
static int __inline__ ov9650_check(void)
{
u32 mid;
mid = sccb_read(OV9650_SCCB_ADDR, 0x1c)<<8;
mid |= sccb_read(OV9650_SCCB_ADDR, 0x1d);
printk("SCCB address 0x%02X, manufacture ID 0x%04X, expect
0x%04X\n", OV9650_SCCB_ADDR, mid,
OV9650_MANUFACT_ID);
return (mid==OV9650_MANUFACT_ID)?1:0;
}
然后再读取该产品的 ID号:
static u32 __inline__ show_ov9650_product_id(void)
{
u32 pid;
pid = sccb_read(OV9650_SCCB_ADDR, 0x0a)<<8;
pid |= sccb_read(OV9650_SCCB_ADDR, 0x0b);
printk("SCCB address 0x%02X, product ID 0x%04X, expect
0x%04X\n", OV9650_SCCB_ADDR, pid, OV9650_PRODUCT_ID);
OV9650_SCCB_
ADDR定义为设备从地址:
0x60!OV9650 有两个只读寄
存器 —— 0x1C 和 0x1D ,
用于存放厂家 ID,数据分别
0x7F和 0xA2 。
该函数是检测从 ov9650芯读
回来的制造厂商的 ID与我们
定义的是否一样,以决定是
否调用该驱动!
批注[A10]:
return pid;
}
读取一切正确后我们来配置寄存器,将刚才定义的结构体里的值依据
前面的地址一次写进寄存器中,实现 OV9650 intialization parameter
table for SXGA(1024*1280) application :
static void ov9650_init_regs(void)
{
int i;
down(®s_mutex);
for (i=0; i
标准的函数
可以调用,因此在这里就不过多介绍。
前面已经介绍过,摄像接口都是通过 DMA 实现数据交换的。在
DMA 数据传递中,还要让 DMA 知道如何进行传递,即一次传输多
少个字节,这需要设置预览 DMA 控制相关寄存器 CIPRCTRL 的主
突发长度和剩余突发长度,这两个值也可以通过调用标准函数来求
得。另外在完成每一帧视频采集后,会触发一个视频中断。
接着有初始化了一个缓冲结构体,开辟四块内存:
struct s3c2440camif_buffer img_buff[] =
{
{
.state = CAMIF_BUFF_INVALID,
.img_size = 0,
.order = 0,
.virt_base = (unsigned long)NULL,
.phy_base = (unsigned long)NULL
},
{
.state = CAMIF_BUFF_INVALID,
s3c2440 能够在
内存中各开辟四块乒乓存储
区域,用于实现 P 通道和 C
通道的快速数据传递。在 P
通道中,寄存 器
CIPRCLRSA1 、
CIPRCLRSA2 、
CIPRCLRSA3 和
CIPRCLRSA4 分别用于表
示这四块内存的首地址!
批注[A11]:
给每块内存先提
供一个状态,其中状态可以
有:enum{
CAMIF_BUFF_INVALID =
0,
CAMIF_BUFF_RGB565 =
1,
CAMIF_BUFF_RGB24 = 2,
CAMIF_BUFF_YCbCr420
= 3,
CAMIF_BUFF_YCbCr422
= 4
};然后大小初始化为 0,
物理与虚拟地址待定,这待
会通过系统直接赋值!
批注[A12]:
.img_size = 0,
.order = 0,
.virt_base = (unsigned long)NULL,
.phy_base = (unsigned long)NULL
},
{
.state = CAMIF_BUFF_INVALID,
.img_size = 0,
.order = 0,
.virt_base = (unsigned long)NULL,
.phy_base = (unsigned long)NULL
},
{
.state = CAMIF_BUFF_INVALID,
.img_size = 0,
.order = 0,
.virt_base = (unsigned long)NULL,
.phy_base = (unsigned long)NULL
}
};
现在我们开始从驱动模块的最开始执行的函数讲起:
static int __init camif_init(void)
{
int ret;
struct s3c2440camif_dev * pdev;
struct clk * camif_upll_clk;
printk(KERN_ALERT"initializing s3c2440 camera interface......\n");
pdev = &camera;
/* set gpio-j to camera mode. */
s3c2410_gpio_cfgpin(S3C2440_GPJ0,
S3C2440_GPJ0_CAMDATA0);
.................
/* init camera's virtual memory. */
if (!request_mem_region((unsigned long)S3C2440_PA_CAMIF,
S3C2440_SZ_CAMIF, CARD_NAME))
{
ret = -EBUSY;
goto error1;
}
配置 GPIO口的 J
部分引脚为摄像头模式!
批注[A13]:
给摄像头分配并
初始化虚拟内存,地址从
0x4F000000开始并大小 1M!
批注[A14]:
/* remap the virtual memory. */
camif_base_addr = (unsigned long)ioremap_nocache((unsigned
long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF);
if (camif_base_addr == (unsigned long)NULL)
{
ret = -EBUSY;
goto error2;
}
/* init camera clock. */
pdev->clk = clk_get(NULL, "camif");
if (IS_ERR(pdev->clk))
{
ret = -ENOENT;
goto error3;
}
clk_enable(pdev->clk);
camif_upll_clk = clk_get(NULL, "camif-upll");
clk_set_rate(camif_upll_clk, 24000000);
mdelay(100);
重映射虚拟内存!
返回映射后的地址!
批注[A15]:
这里初始化外设
的时钟,并将该外设的时钟
插入到外设时钟链表里,以
后有用到这时钟时仅需更根
据后面的 ID即“camif”来辨
认!然后
clk_enable(pdev->clk);便使能
了时钟!
批注[A16]:
不知道怎么计算
出这个 24MHz的?
批注[A17]:
/* init reference counter and its mutex. */
mutex_init(&pdev->rcmutex);
pdev->rc = 0;
/* init image input source. */
pdev->input = 0;
/* init camif state and its lock. */
pdev->state = CAMIF_STATE_FREE;
/* init command code, command lock and the command wait queue.
*/
pdev->cmdcode = CAMIF_CMD_NONE;
init_waitqueue_head(&pdev->cmdqueue);
/* register to videodev layer. */
if (misc_register(&misc) < 0)
{
ret = -EBUSY;
goto error4;
}
printk(KERN_ALERT"s3c2440 camif init done\n");
接下去初始化结
构体,包括初始化互斥锁、
输入格式,状态、命令等!
批注[A18]:
然后将该设备注
册为混杂设备!其中的行为
只有三个函数:
static struct file_operations
camif_fops =
{
.owner =
THIS_MODULE,
.open = camif_open,
.release = camif_release,
.read = camif_read,
};
批注[A19]:
sccb_init();
hw_reset_camif();
has_ov9650 = s3c2440_ov9650_init() >= 0;
s3c2410_gpio_setpin(S3C2410_GPG(4), 1);
return 0;
error4:
clk_put(pdev->clk);
error3:
iounmap((void *)camif_base_addr);
error2:
release_mem_region((unsigned long)S3C2440_PA_CAMIF,
S3C2440_SZ_CAMIF);
error1:
return ret;
}
清除函数就不详细解析了!都是些释放内存,注销注册之类的函数!
现在先讲一下 open函数!
static int camif_open(struct inode *inode, struct file *file)
{
struct s3c2440camif_dev *pdev;
SCCB总线初始
化!
批注[A20]:
硬件复位摄像头!批注[A21]:
初始化 ov9650芯
片!
批注[A22]:
这条语句有什么
用?待解?
批注[A23]:
struct s3c2440camif_fh *fh;
int ret;
if (!has_ov9650) {
return -ENODEV;
}
pdev = &camera;
fh = kzalloc(sizeof(*fh),GFP_KERNEL); // alloc memory for
filehandle
if (NULL == fh)
{
return -ENOMEM;
}
fh->dev = pdev;
pdev->state = CAMIF_STATE_READY;
init_camif_config(fh);
注意这个结构体!
内核中注释为打开文件操作
的结构体!其中的变量除了
s3c2440camif_dev,还有
master变量,这个变量内核注
释为:操作标志,仅仅操作
那些能执行“set”控制的文
件!
批注[A24]:
这个是 ov9650初
始化函数返回的值,如果没
有则为 0!
批注[A25]:
用 kzalloc申请
内存的时候, 效果等同于先
是用 kmalloc() 申请空间 ,
然后用 memset() 来初始
化 ,所有申请的元素都被初
始化为 0.
批注[A26]:
现在可以将摄像
头状态置为已经准备好了!
批注[A27]:
配置摄像头,其实
是填充 s3c2440camif_dev结
构体,比如将原视频尺寸设
为 1024*1280,即 SXGA的
标准!还有编码目标的尺寸
和预览目标的尺寸!
批注[A28]:
ret = init_image_buffer(); // init image buffer.
if (ret < 0)
{
goto error1;
}
request_irq(IRQ_S3C2440_CAM_C, on_camif_irq_c,
IRQF_DISABLED, "CAM_C", pdev); // setup ISRs
if (ret < 0)
{
goto error2;
}
request_irq(IRQ_S3C2440_CAM_P, on_camif_irq_p,
IRQF_DISABLED, "CAM_P", pdev);
if (ret < 0)
{
goto error3;
}
clk_enable(pdev->clk); // and enable camif clock.
soft_reset_camif();
初始化视频缓冲
区!该函数首先计算出编码
通道与预览通道需要的大
小,然后取其最大的!接着
用到一个函数 get_order,这
个函数是用于当你用字节表
示内存大小时!然后填充缓
冲区的变量,有四个,比如
开辟的内存区 1中有:
img_buff[0].order 、
img_buff[0].virt_base 、
img_buff[0].phy_base等,
就是物理地址、虚拟地址、
还有 order参数,这个在获
得分配的页内存函数
__get_free_pages中用到!
批注[A29]:
之后注册两个重
要的中断函数,并初始化时
钟和软复位摄像头!
批注[A30]:
file->private_data = fh;
fh->dev = pdev;
update_camif_config(fh, 0);
return 0;
error3:
free_irq(IRQ_S3C2440_CAM_C, pdev);
error2:
free_image_buffer();
error1:
kfree(fh);
return ret;
}
然后是读函数:
static ssize_t camif_read(struct file *file, char __user *data, size_t count,
loff_t *ppos)
{
int i;
struct s3c2440camif_fh * fh;
struct s3c2440camif_dev * pdev;
在这里将 fh作为
私有变量赋给 file结构体,以
后可能会用到!
批注[A31]:
记得再次更新配
置寄存器否则要是刚才的寄
存器遭到修改的话,结果就
与我们要求的相悖了!
批注[A32]:
fh = file->private_data;
pdev = fh->dev;
if (start_capture(pdev, 0) != 0)
{
return -ERESTARTSYS;
}
disable_irq(IRQ_S3C2440_CAM_C);
disable_irq(IRQ_S3C2440_CAM_P);
for (i = 0; i < 4; i++)
{
if (img_buff[i].state != CAMIF_BUFF_INVALID)
{
copy_to_user(data, (void *)img_buff[i].virt_base, count);
img_buff[i].state = CAMIF_BUFF_INVALID;
}
}
enable_irq(IRQ_S3C2440_CAM_P);
enable_irq(IRQ_S3C2440_CAM_C);
刚才讲到的私有
数据现在用到了!这里为什
么要这样做,待会再解释!
批注[A33]:
开始采集图像传
到用户空间!
批注[A34]:
关闭中断!批注[A35]:
将所得的数据在
内存的四个区域依次拷贝到
用户空间里!然后将内存区
域重新置为不可用状态!
批注[A36]:
接着在使能中断,
读取下一帧数据!
批注[A37]:
return count;
}
对于 order变量还有以下知识:
order 是你在请求的或释放的页数的以 2 为底的对数(即, log2N). 例
如, 如果你要一个页 order 为 0, 如果你请求 8 页就是 3. 如果
order 太大(没有那个大小的连续区可用), 页分配失败. get_order 函
数, 它使用一个整数参数, 可以用来从一个 size 中提取 order(它必
须是 2 的幂)给主机平台. order 允许的最大值是 10 或者 11 (对应
于 1024 或者 2048 页), 依赖于体系. 但是, 一个 order-10 的分配
在除了一个刚刚启动的有很多内存的系统中成功的机会是小的.
接着是释放函数,没什么好讲的,注意释放内存就是。现在重点讲解
中断函数以及采集视频函数!
我们这里拿预览中断函数来做例子!
static irqreturn_t on_camif_irq_p(int irq, void * dev)
{
u32 ciprstatus;
u32 frame;
struct s3c2440camif_dev * pdev;
ciprstatus = ioread32(S3C244X_CIPRSTATUS);
if ((ciprstatus & (1<<21))== 0)
CIPRSTATUS反
映 P通道数据的写入状态,
即 4组缓冲的哪一组。用户
程序采集图像数据时,应根
据状态寄存器当前状态,决
定从哪一组读出数据。
批注[A38]:
{
return IRQ_RETVAL(IRQ_NONE);
}
pdev = (struct s3c2440camif_dev *)dev;
/* valid img_buff[x] just DMAed. */
frame = (ciprstatus&(3<<26))>>26;
frame = (frame+4-1)%4;
img_buff[frame].state = CAMIF_BUFF_RGB565;
if (pdev->cmdcode & CAMIF_CMD_STOP)
{
stop_capture(pdev);
pdev->state = CAMIF_STATE_READY;
}
else
{
if (pdev->cmdcode & CAMIF_CMD_P2C)
{
camif_c2p(pdev);
从这个计算
计算出哪组可以读出数据!
为什么这么算请参考
S3C2440的技术手册第 542
页!
批注[A39]:
将该组的状态置
为可以读出 RGB565格式的
数据!
批注[A40]:
接下去的就是查
看有什么动作要做,比如是
不是停止采集了还是有预览
模式转为编码模式之类的判
断!
批注[A41]:
}
if (pdev->cmdcode & CAMIF_CMD_WND)
{
update_target_wnd_regs(pdev);
}
if (pdev->cmdcode & CAMIF_CMD_TFMT)
{
update_target_fmt_regs(pdev);
}
if (pdev->cmdcode & CAMIF_CMD_ZOOM)
{
update_target_zoom_regs(pdev);
}
invalid_image_buffer();
}
pdev->cmdcode = CAMIF_CMD_NONE;
wake_up(&pdev->cmdqueue);
指所有的缓冲区
状态为不可用!
批注[A42]:
唤醒等待队列!批注[A43]:
return IRQ_RETVAL(IRQ_HANDLED);
}
编码通道的中断函数也与此类似!
接下去讲一下数据采集函数:
static int start_capture(struct s3c2440camif_dev * pdev, int stream)
{
int ret;
u32 ciwdofst;
u32 ciprscctrl;
u32 ciimgcpt;
ciwdofst = ioread32(S3C244X_CIWDOFST);
ciwdofst |= (1<<30) // Clear the overflow indication flag of input
CODEC FIFO Y
|(1<<15) // Clear the overflow indication flag of input CODEC
FIFO Cb
|(1<<14) // Clear the overflow indication flag of input CODEC
FIFO Cr
|(1<<13) // Clear the overflow indication flag of input
PREVIEW FIFO Cb
|(1<<12);// Clear the overflow indication flag of input PREVIEW
CIWDOFST:从输
入信号中截取中心部分的图
像寄存器
原理是:从输入信号中
截取中心部分的图像输出到
大小不变的输出图像缓冲
中,从而实现对图像的放大
或缩小。
①配置该寄存器允许缩放或
不允许缩放。
②X方向图像放大或缩小控
制,原理是截切掉左部和右
部的图像像素数。
③Y方向图像放大或缩小控
制,原理是截切掉上部和下
部的图像像素数。
批注[A44]:
FIFO Cr
iowrite32(ciwdofst, S3C244X_CIWDOFST);
ciprscctrl = ioread32(S3C244X_CIPRSCCTRL);
ciprscctrl |= 1<<15; // preview scaler start
iowrite32(ciprscctrl, S3C244X_CIPRSCCTRL);
pdev->state = CAMIF_STATE_PREVIEWING;
ciimgcpt = (1<<31) // camera interface global capture
enable
|(1<<29); // capture enable for preview scaler.
iowrite32(ciimgcpt, S3C244X_CIIMGCPT);
ret = 0;
if (stream == 0)
{
pdev->cmdcode = CAMIF_CMD_STOP;
ret = wait_event_interruptible(pdev->cmdqueue, pdev->cmdcode
== CAMIF_CMD_NONE);
}
预览模式控制寄
存器,第 15位置一代表启动
预览模式!
批注[A45]:
这个寄存器必须
最后设置,是图像采集使能
寄存器,29位是使能预览模
式,31位是使能全局摄像头
接口!
批注[A46]:
该函数的形参,在
read函数中调用到这个函数,
其中 stream是 0,所以会进入
这个 if语句中,然后就等待
中断了!只要中断已发生,
从摄像头采集到数据,就可
以立刻返回,执行到 read中
接下去的程序,将数据读回
到用户空间!
批注[A47]:
return ret;
}
以上便是 CMOS摄像头驱动的整个编写思路,其中还有一些函数未
能举例出来,大家可以下去好好看看!
小兀
2012年 6月 10号
如有引用请注明出处!