基于2.6.35内核的OV9650摄像头驱动移植

作者:冯利美,华清远见嵌入式学院讲师。

一、 移植环境:

【移植环境】

1、 主机:Ubuntu 10.10发行版

2、 目标机:FS_S5PC100平台

3、 交叉编译工具:arm-none-linux-gnueabi-4.5.1

4、 摄像头模块:OV9650

5、Linux文件系统:rootfs. cramfs.

6、内核:linux-2.6.35-farsigt

二、 内核配置

1.修改vi drivers/i2c/busses/Kconfig

(参考实验二十六 I2C驱动编写及测试)

修改

config I2C_S3C2410
                tristate "S3C2410 I2C Driver"
                depends on ARCH_S3C2410 || ARCH_S3C64XX
                help
                        Say Y here to include support for I2C controller in the
                        Samsung S3C2410 based System-on-Chip devices.

为:

config I2C_S3C2410
                tristate "S3C2410 I2C Driver"
                depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100
                help
                        Say Y here to include support for I2C controller in the
                        Samsung S3C2410 based System-on-Chip devices.

2.内核配置并重新编译内核
        $ make menuconfig
         Device Drivers --->
                <*> I2C support --->
                        <*> I2C device interface
                                I2C Hardware Bus support --->
                                        <*> S3C2410 I2C Driver

3.修改vi arch/arm/mach-s5pc100/mach-smdkc100.c

查看原理图可以知道我们的摄像头是接在I2C-1上所以修改i2c_devs1添加ov9650的内容,主要是ov9650的地址,这个在芯片手册上可以查到是0x30

修改:
        static struct i2c_board_info i2c_devs1[] __initdata = {
        };

为:
        static struct i2c_board_info i2c_devs1[] __initdata = {
                {
                I2C_BOARD_INFO("ov9650", 0x30),
                },
        };

添加s5pc100 摄像头控制器平台设备相关内容,这些内容我们可以通过查看S5PC100的芯片手册查到
        static struct resource s3c_camif_resource[] = {
                [0] = {
                        .start = 0xEE200000,
                        .end = 0xEE200000 + SZ_1M - 1,
                        .flags = IORESOURCE_MEM,
                },
                [1] = {
                        .start = IRQ_FIMC0,
                        .end = IRQ_FIMC0,
                        .flags = IORESOURCE_IRQ,
                }

};

static u64 s3c_device_camif_dmamask = 0xffffffffUL;

struct platform_device s3c_device_camif = {
                 .name = "s5pc100-camif",
                .id = 0,
                .num_resources = ARRAY_SIZE(s3c_camif_resource),
                 .resource = s3c_camif_resource,
                 .dev = {
                         .dma_mask = &s3c_device_camif_dmamask,
                         .coherent_dma_mask = 0xffffffffUL
                 }
         };

EXPORT_SYMBOL(s3c_device_camif);

注册摄像头控制平台设备:

在smdkc100_devices中添加s3c_device_camif
        static struct platform_device *smdkc100_devices[] __initdata = {
                &s3c_device_camif, //添加内容
        };

4. 添加驱动(video)
        Make menuconfig
         Device Drivers ---> 
                <*> Multimedia support --->
                         <*> Video For Linux 
         [*] Enable Video For Linux API 1 (DEPRECATED) (NEW)
         [*] Video capture adapters (NEW) --->
         [*] V4L USB devices (NEW) ---> 
        <*> USB Video Class (UVC) 
         [*] UVC input events device support (NEW)
         <*> USB ZC0301[P] webcam support (DEPRECATED)

三、 驱动编写

四、 网络视频服务器移植

对于内核linux-2.6.35,不能再使用servfox,如果要使用的需要修改大量的代码,在此选用新的方法来移植。需要两个文件jpeg,mjpg-stream.移植如下:

mkdir /source/rootfs/mjpg

1. jpeg库的移植

mkdir /home/linux/jpeg

jpeg源码包通过下面这个网址下载

http://www.ijg.org/files/jpegsrc.v8b.tar.gz

解压源码包

tar xvf jpegsrc.v8b.tar.gz

配置源码

cd jpeg-8b
        ./configure --prefix=/home/linux/jpeg --host=arm-none-linux-gnueabi
        ./configure --prefex=/home/linux/jpeg --host=arm-none-linux-gnueabi

(./configure 配置源代码树 
        安装目录:
        --prefix=PREFIX
        体系无关文件的顶级安装目录PREFIX ,也就Apache的安装目录。[/usr/local/apache2]

系统类型:
        --host=HOST

指定Apache HTTP服务器将要运行的目标系统类型HOST 。

[BUILD]

)

编译

make

安装

make install

拷贝库到文件系统中

cp /home/linux/jpeg/lib/libjpeg.so.8 /source/rootfs/lib

2. mjpg-stream的移植

mjpg-stream源码包通过下面这个网址下载
        http://sourceforge.net/projects/mjpg-streamer/

解压源码

tar xvf mjpg-streamer-r63.tar.gz

修改源码

cd mjpg-streamer-r63

修改顶层makefile及plugins目录中的各级makefile将所有 (注意这里需要修改的内容)

CC=gcc

修改为

cc=arm-none-linux-gnueabi-gcc

修改plugins/input_uvc/Makfile

修改

CFLAGS += -O2 -DLINUX -D_GNU_SOURCE -Wall -shared -fPIC

CFLAGS += -O2 -DLINUX -D_GNU_SOURCE -Wall -shared –fPIC –I/homw/linux/jpeg/include

修改

$(CC) $(CFLAGS) -ljpeg -o $@ input_uvc.c v4l2uvc.lo jpeg_utils.lo dynctrl.lo

$(CC) $(CFLAGS) -ljpeg -L/home/linux/jpeg/lib -o $@ input_uvc.c v4l2uvc.lo jpeg_utils.lo dynctrl.lo

3. 拍照功能的实现

由于mjpg_stream中output-file.so能实现连续拍照的功能,不能实现单拍或连拍几张的功能所以需要对output_file原码进行修改。
         # cd mjpg-streamer-rc63/plugins/output_file
        # vim output_file.c
        在96行 函数 void *worker_thread(void *arg) 体中加入以下代码:
        char buf[10]; //用于存放从管道读取的命令
        int flags = 0; //拍照标志,1:表示11张照片,2:表示1张照片
        int fd_com = 0; //打开管道的文件描述符

stop_num = 0; //拍照计数
        if ( access(“/tmp/webcom”,F_OK) < 0 ) //创建有名管道用于接收拍照命令
        {
        if ( mkfifo(“/tmp/webcom”,0666 ) < 0)
        {
        Printf(“ photo fifo create failed\n”);
        }
        }
        fd_com = open (“/tmp/webcom”,O_RDONLY,0666);
        if (fd < 0)
        {
        perror (“open the file webcom error”);
        }

在while( ok >= 0 && !pglobal->stop){ 后加入
        if (flags == 0)
        {
        while(1)
        {
        reade(fd_com,buf,sizeof(buf));
        if (strncmp(buf,”danger”,6) == 0) //拍11张照片
        {
        flags = 1;
        bzero(buf,sizeof(buf));
        break;
         }
        if (strncmp(buf,”one”,3) == 0) //拍1张照片
        {
        flags = 2;
        bzero(buf,sizeof(buf));
        break;
        }

}
        }

在if (delay > 0){
        usleep(1000*delay);
        }后加入
        stop_num++
        if (flags == 1) //判断拍照的数量
        {
         if ( stop_num > 9)
        {
        stop_num = 0;
        flsgs = 0;
        }
         }
         else if (flags == 2)
         {
         stop_num = 0;
         flags = 0;
        }

编译

(make clean)
        Make
        mkdir /source/rootfs/pice
        mkdir /source/rootfs/mjpg
        cp *.so /source/rootfs/mjpg
        cp mjpg_streamer /source/rootfs/bin
        cp /home/linux/newyizhi_first/toolchain/arm-2010.09/arm-none-linux-gnueabi/libc/lib/* /source/rootfs/lib/

4. 将摄像头驱动s5pc100_cam.ko拷贝到 /source/rootfs目录下

所以只要向有名管道/tmp/webcom写入danger就能连拍11张照片,写入one就拍一张照片。

执行命令:
        mjpg_streamer -i "/mjpg/input_uvc.so -y" -o "/mjpg/output_file.so -f /pice -d 1000 " &

拍照一张:
        echo one > /tmp/webcom

五、 文件系统的制作

支持U盘自动挂载,需做以下步骤:

1、下载UDEV源码udev-080.tar.bz2,网址:

http://www.us.kernel.org/pub/linux/utils/kernel/hotplug/,并解压。

2、交叉编译。

修改Makefile,具体修改如下:

CROSS = arm-none-linux-gnueabi-

保存退出。

修改udevmointer.c,添加 #include <stddef.h>

执行命令:make 进行编译,然后执行arm-none-linux-gnueabi-strip udev udevd udevstart udevinfo udevtest。并拷贝这些文件到rootfs/bin目录下面。

3、修改/source/rootfs/etc/fstab为
         #device mount-point type options dump fsck order
        proc /proc proc defaults 0 0
        tmpfs /tmp tmpfs defaults 0 0
        sysfs /sys sysfs defaults 0 0 
        tmpfs /dev tmpfs defaults 0 0

4、修改/source/rootfs/etc/init.d/rcS,添加如下内容

/bin/udevd --daemon

/bin/udevstart

这样当我们插上U盘和SD卡的时候,就可以看到我们的设备节点而不需要手动创建设备节点。

5、SD卡或U盘自动挂载的实现

拷贝实验代码中udev目录到 /source/rootfs/etc

拷贝mount-sd.sh、 umount-sd.sh 、mount-usb.sh、lh到rootfs/sbin下

这个时候当插入U盘或SD卡时就会自动挂载在tmp下。可以通过修改mount-sd.sh和mount-usb.sh添加其他功能和修改挂载点。

六、 移植过程中出现的相关问题及解决:

1、 移植streamer的过程中 make出现 无法找到ljpeg库的问题

解决:查看 jpeg—lib 目录下 file libjpeg.so.8.0.2 是否是ARM体系

如果是386的,可能交叉编译工具链的问题,建议拷贝其他可用的jpeg库。

2、出现Init V4L2 failed opening V4L interface:NO such file

如果内核配置正确,一般都会支持V4L2, 我重启之后就再也没有出现这个问题

3.、streamer snapshot 未能正常显示

请确定加 –y 指令

如果在Window下正在使用viewer.exe 软件测试streamer,先关闭或者暂停该软件,然后再在浏览器中输入。。。。。测试

4、摄像头显示的内容是红色

因为内核里,没有配置I2C的驱动

5、拍照测试程序,拍摄前7张时不能正常显示

尚待解决

V4L2的使用说明

请参看 “相关资料”——“摄像头相关资料(v4l2)”

ov9650 的配置

经过4天的调试摄像头终于可以拍照片保存到电脑上来,ov9650的调试走了不少弯路,一些教训总结如下:
1:OV9650是OmniVision公司的COMS摄像头,号称有130万像素,但是实际效果感觉不如CCD的,特别是远处的背景更糟糕。
2:OV9650的datasheet上写的寄存器控制协议是Serial Camera Control Bus (SCCB),还给了个时序图,但是它用的是两线制  的SCCB协议;根据它的时序图是可以兼容I2C的,两者在细微之处有点差别,但不影响,但实际应用中,都是后端平台的  I2C接camera的SCCB。可惜我没有仔细分析,愣是用两个GPIO模拟SCCB通信协议。此乃一错
3:对于OV9650,OmniVision公司是提供了参考寄存器配置的!一定要找相关人员要!(可能是他们的datasheet写得太烂的原因吧),可以我过分相信自己,没有找技术支持沟通!此乃二错
4:ov9650复位后默认配置是不能收到图像的!(在我的这个模式下:YUV422,VGA),当我在用I2C配置时,有的像的轮廓但颜色偏绿,我以为是没有配置起寄存器,但是实际上是配置起寄存器了,是YUV的顺序有问题!此乃三错
5:我们用的是杰得提供的VIA模块控制API,杰得的API多、复杂、有错且没有系统的说明,又由于我没有调试摄像头的相关经验,也没有弄明白杰得的VIA模块API。所以调试的时候没有图像,到底是摄像头输出问题?还是Z228接收处理的问题?无法判断!调试完摄像头了才知道杰得的库是可以输入4:2:2,输出4:2:0的!


我觉得调试步骤应该如下:
一:先要保证硬件没有问题!
1:首先要看RESET脚和,PDN脚.的电平是否正确,RESET:高电平复位.PDN:低电平工作正常。
    2:第一次调试I2C总线的时候,把示波器放在数据线上,抓图分析看OV9650是否应答。地址应该是60H不要搞错了!
    3:测试关键信号(这一小步要寄存器配置正确芯片正常工作了才有的):
           时钟输入信号pin13: 24M正弦波(外部晶振的输入)。
           时钟输出信号(像素频率):24M,变形了的像正弦波的方波。
           场同步信号Vsync:形如:   ----_----------_-----  :两个低电平之间是20ms左右(我测试的是22ms)
           行同步信号Href: 形如:   ----_----------_-----   :两个低电平之间是64us左右(我测试的是15.9Khz)
           以上三个信号是电视信号标准,一定要对。但是,在测试的时候有时候信号不是这个值,比如Vsync=34ms,why?我还没有搞清楚,请高手指点.
二:OV寄存器配置:
1:如果没有测试硬件,千万不要妄想配置寄存器! 也是我的调试原则,可惜这次我又违背了!嘚反省!
2: 用I2C读取OV9650的ProducaitonID寄存器【0x0A】【0x0B】:读出来版本号应该是0x9652,这里也一定要对,特别是你手上有几个信号的OV芯片的时候,不同型号的oV芯片,RESET复位信号不同,(有高有低哦!有一个网友就犯了这个错误浪费了四天),datasheet上没有说的很明白Reset是高复位还是低复位,我的小窍门是reset引脚内部有上拉电阻的话应该是低电平复位,有内部下拉电阻的话应该是高电平复位。
3:证明能够通过I2C控制OV9650后,将厂家给的或者网上下的相应的配置写入到ov9650。
4:如果配置大体正确,现在摄像头的数据应该出来了。


==============
以下是厂家给的配置:共有三个VGA,CIF,SXVGA,输出都是YUV模式,如是时钟是24M,帧率是15fps,我只验证了VGA模式的,效果还可以!
;09032004
;OV9650
;VGA YUV
;15fps when 24MHz input clock
;Device Address(Hex)/Register(Hex)/Value(Hex)
;
60 12 80
60 11 81
60 6b 0a
60 6a 3e
60 3b 09
60 13 e0
60 01 80
60 02 80
60 00 00
60 10 00
60 13 e5
;
60 39 43 ;50 for 30fps
60 38 12 ;92 for 30fps
60 37 00
60 35 91 ;81 for 30fps
60 0e 20
60 1e 04
;
60 A8 80
60 12 40
60 04 00
60 0c 04
60 0d 80
60 18 c6
60 17 26
60 32 ad
60 03 00
60 1a 3d
60 19 01
60 3f a6
60 14 2e
60 15 02
60 41 02
60 42 08
;
60 1b 00
60 16 06
60 33 e2 ;c0 for internal regulator
60 34 bf
60 96 04
60 3a 00
60 8e 00
;
60 3c 77
60 8B 06
60 94 88
60 95 88
60 40 c1
60 29 3f ;2f for internal regulator
60 0f 42
;
60 3d 92
60 69 40
60 5C b9
60 5D 96
60 5E 10
60 59 c0
60 5A af
60 5B 55
60 43 f0
60 44 10
60 45 68
60 46 96
60 47 60
60 48 80
60 5F e0
60 60 8c ;0c for advanced AWB (related to lens)
60 61 20
60 a5 d9
60 a4 74
60 8d 02
60 13 e7
;
60 4f 3a
60 50 3d
60 51 03
60 52 12
60 53 26
60 54 38
60 55 40
60 56 40
60 57 40
60 58 0d
;
60 8C 23
60 3E 02
60 a9 b8
60 aa 92
60 ab 0a
;
60 8f df
60 90 00
60 91 00
60 9f 00
60 a0 00
60 3A 01
;
60 24 70
60 25 64
60 26 c3
;
60 2a 00 ;10 for 50Hz
60 2b 00 ;40 for 50Hz
;
;gamma
60 6c 40
60 6d 30
60 6e 4b
60 6f 60
60 70 70
60 71 70
60 72 70
60 73 70
60 74 60
60 75 60
60 76 50
60 77 48
60 78 3a
60 79 2e
60 7a 28
60 7b 22
60 7c 04
60 7d 07
60 7e 10
60 7f 28
60 80 36
60 81 44
60 82 52
60 83 60
60 84 6c
60 85 78
60 86 8c
60 87 9e
60 88 bb
60 89 d2
60 8a e6
;


==============
;09032004
;OV9650
;CIF YUV
;15fps when 24MHz input clock
;Device Address(Hex)/Register(Hex)/Value(Hex)
;
60 12 80
60 11 83
60 6b 0a
60 6a 30
60 3b 09
60 13 e0
60 01 80
60 02 80
60 00 00
60 10 00
60 13 e5
;
60 39 43
60 38 12
60 37 00
60 35 91
60 0e a0
60 1e 04
;
60 A8 80
60 12 20
60 04 00
60 0c 04
60 0d 80
60 18 7e
60 17 26
60 32 24
60 03 36
60 1a 24
60 19 00
60 14 2e
60 15 02
60 3f a6
60 41 02
60 42 08
;
60 1b 00
60 16 06
60 33 e2 ;c0 for internal regulator
60 34 bf
60 96 04
60 3a 00
60 8e 00
;
60 3c 77
60 8B 06
60 94 88
60 95 88
60 40 c1
60 29 3f ;2f for internal regulator
60 0f 42
;
60 3d 92
60 69 40
60 5C b9
60 5D 96
60 5E 10
60 59 c0
60 5A af
60 5B 55
60 43 f0
60 44 10
60 45 68
60 46 96
60 47 60
60 48 80
60 5F e0
60 60 8c ;0c for advanced AWB (related to lens)
60 61 20
60 a5 d9
60 a4 74
60 8d 02
60 13 e7
;
60 4f 3a
60 50 3d
60 51 03
60 52 12
60 53 26
60 54 38
60 55 40
60 56 40
60 57 40
60 58 0d
;
60 8C 23
60 3E 02
60 a9 b8
60 aa 92
60 ab 0a
;
60 8f df
60 90 00
60 91 00
60 9f 00
60 a0 00
60 3A 01
;
60 24 70
60 25 64
60 26 c3
;
60 2a 00 ;00 for 50Hz
60 2b 00 ;D2 for 50Hz
;
;gamma
60 6c 40
60 6d 30
60 6e 4b
60 6f 60
60 70 70
60 71 70
60 72 70
60 73 70
60 74 60
60 75 60
60 76 50
60 77 48
60 78 3a
60 79 2e
60 7a 28
60 7b 22
60 7c 04
60 7d 07
60 7e 10
60 7f 28
60 80 36
60 81 44
60 82 52
60 83 60
60 84 6c
60 85 78
60 86 8c
60 87 9e
60 88 bb
60 89 d2
60 8a e6
;


=================
;09032004
;OV9650
;SXGA YUV
;7.5fps when 24MHz input clock
;Device Address(Hex)/Register(Hex)/Value(Hex)
;
60 12 80
60 11 80
60 6b 0a
60 6a 41
60 3b 09
60 13 e0
60 01 80
60 02 80
60 00 00
60 10 00
60 13 e5
;
60 39 43 ;50 for 15fps
60 38 12 ;93 for 15fps
60 37 00
60 35 91 ;81 for 15fps
60 0e 20
60 1e 04
;
60 A8 80
60 12 00
60 04 00
60 0c 00
60 0d 00
60 18 bd
60 17 1d
60 32 ad
60 03 12
60 1a 81
60 19 01
60 14 2e
60 15 00
60 3f a6
60 41 02
60 42 08
;
60 1b 00
60 16 06
60 33 e2 ;c0 for internal regulator
60 34 bf
60 96 04
60 3a 00
60 8e 00
;
60 3c 77
60 8B 06
60 94 88
60 95 88
60 40 c1
60 29 3f ;2f for internal regulator
60 0f 42
;
60 3d 92
60 69 40
60 5C b9
60 5D 96
60 5E 10
60 59 c0
60 5A af
60 5B 55
60 43 f0
60 44 10
60 45 68
60 46 96
60 47 60
60 48 80
60 5F e0
60 60 8C ;0c for advanced AWB (Related to lens)
60 61 20
60 a5 d9
60 a4 74
60 8d 02
60 13 e7
;
60 4f 3a
60 50 3d
60 51 03
60 52 12
60 53 26
60 54 38
60 55 40
60 56 40
60 57 40
60 58 0d
;
60 8C 23
60 3E 02
60 a9 b8
60 aa 92
60 ab 0a
;
60 8f df
60 90 00
60 91 00
60 9f 00
60 a0 00
60 3A 01
;
60 24 70
60 25 64
60 26 c3
;
60 2a 00 ;10 for 50Hz
60 2b 00 ;34 for 50Hz
;
;gamma
60 6c 40
60 6d 30
60 6e 4b
60 6f 60
60 70 70
60 71 70
60 72 70
60 73 70
60 74 60
60 75 60
60 76 50
60 77 48
60 78 3a
60 79 2e
60 7a 28
60 7b 22
60 7c 04
60 7d 07
60 7e 10
60 7f 28
60 80 36
60 81 44
60 82 52
60 83 60
60 84 6c
60 85 78
60 86 8c
60 87 9e
60 88 bb
60 89 d2
60 8a e6
;
mini2440提供了一个摄像头接口,可以输出RGB24,RGB16的原始图像还可以输出编码的如YUV格式的图像,并提供了偏移翻转,放大缩小的功能。与开发板配套的摄像头模块为CAM130,采用ov9650的芯片。操作摄像头接口实现视频的采集与显示,主要配置两个部分的寄存器:ov9650的寄存器,摄像头接口寄存器。
        ov9650寄存器在模块的内部,s3c2440是以sccb总线来与ov9650通信。sccb总线类似iic总线,而且mini2440摄像头接口的sccb总线就接在了他的iic接口上,所以可以通过Iic来配置ov9650的寄存器,同样也可以用gpio来模拟sccb总线的时序。ov9650有大量的寄存器需要设置,但是一般不需要我们关心,厂商将一些常用的配置写成数组,直接赋值就可以了。需要我们关心的是厂商ID寄存器,这个一般在摄像头初始化之前用来判定摄像头是否正确安装。
        摄像头接口寄存器的配置比较麻烦,大体上需要配置三类的寄存器。第一类是摄像头接口输入视频格式寄存器CISRCFMT,功能是设置摄像头接口的输入数据的格式与长宽。s3c2440的摄像头接口提供了两个DMA通道,一个预览输出DMA,有四块DMA缓冲区,这个通道用于将采集到的视频图像直接显示到Lcd上。另外一个是编码输出DMA,也有四块DMA缓冲区,用于后期处理视频。所以第二类是预览输出视频格式寄存器。这类寄存器主要有以下几个:
CIPRTRGFMT      这个寄存器主要设置目标图像的长宽与是否进行旋转与镜像处理
CIPRCTRL        突发长度寄存器,这里的值与目标图像的宽度有关,有计算公式,不需要关许只需要调用函数计算然后赋值给它就可以了
CIPRSCPRERATIO
CIPRSCPREDST
CIPRSCCTRL        这三个寄存器主要是设置缩放比例以及与缩放偏移相关的参数,需要以原始图像以及目变图像的大小作为参数,通过一定的公式计算得出。公式是固定的直接用就可以了,不需要关心。CIPRSCCTRL这个寄存器尤其重要,因为第30位是视频输出的格式,1为RGB24,0为RGB16格式,需要根据LCD的类型设置。我的mini2440采用的T35的lcd就是RGB16的屏,所以这里需要设置成0,否则输出到lcd的图像颜色就不对,如果是RGB24的屏,则第30位要设置成1。
CIPRTAREA    目标图像面积寄存器,这个主要用于决定DMA一次传送的数据量
CIPRCLRSA1
CIPRCLRSA2
CIPRCLRSA3
CIPRCLRSA4   这四个寄存器是DMA目的寄存器,也就是摄像头接口输出存放数据的地方,如果直接显示的话就要都赋值成Lcd的帧缓冲首地址
        第三类寄存器是编码输出视频格式寄存器,与第二类相似。摄像头接口还有一些功能寄存器需要设置,如时钟寄存器CAMDIVN,与控制寄存器CIGCTRL,负责设置信号的极性以及摄像头的复位。综上所述,操作摄像头需要以下几个步骤:

    (1) 读取厂商ID,来判定摄像头是否安装正确,这首先需要正确操作sccb总线,这一步也可以用来判定sccb总线时序是否正确。   

    (2) 配置摄像头,事先将寄存器的地址以及对应的值写入数组,直接通过循环完成配置,配置完成之后就不需要操作sccb总线了,也就是不需要管ov9650这端了。以下的操作设置摄像头接口这端的寄存器就可以了。

    (3) 设置摄像头所需的时钟,设置控制寄存器,复位摄像头。
    (4) 根据lcd的类型以及需要显示视频的大小,设置源图像寄存器,预览输出寄存器等
        以上是使用摄像头接口的预览输出功能的基本操作步骤,还有每采集一帧图像,摄像头都会产生中断。具体程序在我的下载资源里:http://download.csdn.net/detail/yaozhenguo2006/4001720 是在ubuntu下用amr-linux-gcc编译的,如果安装了arm-linux-gcc并且将命令导入到了环境变量里,直接在代码目录下make就可以了,程序参考了http://blog.csdn.net/zhaocj/article/details/5653479,感谢博主对摄像头详尽的讲述。在mini2440开发板上(配套lcd为统宝3.5寸屏)程序是可以正确运行的,如果其他的屏幕需要改一下参数。

学习了裸机OV9650P通道LCD直接显示程序,作为这点基础开始分析OV9650linux设备驱动程序。

我们跟踪程序按照常规方法,跟着驱动的编写脉络去读程序。

1、在程序中找到程序入口函数——加载和卸载module_initmodule_exit

 

 

[c-sharp] view plaincopyprint?
  1. module_init(camif_init);  
  2. module_exit(camif_cleanup);  

通过这个入口函数我们找到了函数加载和卸载函数的定义,首先我们分析加载函数(由于函数的嵌套性,我们这里用序号来体现嵌套)

2、camif_init跟踪

 

[c-sharp] view plaincopyprint?
  1. static int __init camif_init(void)  
  2. {  
  3.     int ret;  
  4.     struct tq2440_camif_dev * pdev;  
  5.     struct clk * camif_upll_clk;  
  6.   
  7.     printk(KERN_ALERT"initializing s3c2440 camera interface....../n");  
  8.   
  9.     pdev = &camera;  
  10.   
  11.       
  12.     s3c2410_gpio_cfgpin(S3C2440_GPJ0, S3C2440_GPJ0_CAMDATA0);  
  13.     s3c2410_gpio_cfgpin(S3C2440_GPJ1, S3C2440_GPJ1_CAMDATA1);  
  14.     s3c2410_gpio_cfgpin(S3C2440_GPJ2, S3C2440_GPJ2_CAMDATA2);  
  15.     s3c2410_gpio_cfgpin(S3C2440_GPJ3, S3C2440_GPJ3_CAMDATA3);  
  16.     s3c2410_gpio_cfgpin(S3C2440_GPJ4, S3C2440_GPJ4_CAMDATA4);  
  17.     s3c2410_gpio_cfgpin(S3C2440_GPJ5, S3C2440_GPJ5_CAMDATA5);  
  18.     s3c2410_gpio_cfgpin(S3C2440_GPJ6, S3C2440_GPJ6_CAMDATA6);  
  19.     s3c2410_gpio_cfgpin(S3C2440_GPJ7, S3C2440_GPJ7_CAMDATA7);  
  20.     s3c2410_gpio_cfgpin(S3C2440_GPJ8, S3C2440_GPJ8_CAMPCLK);  
  21.     s3c2410_gpio_cfgpin(S3C2440_GPJ9, S3C2440_GPJ9_CAMVSYNC);  
  22.     s3c2410_gpio_cfgpin(S3C2440_GPJ10, S3C2440_GPJ10_CAMHREF);  
  23.     s3c2410_gpio_cfgpin(S3C2440_GPJ11, S3C2440_GPJ11_CAMCLKOUT);  
  24.     s3c2410_gpio_cfgpin(S3C2440_GPJ12, S3C2440_GPJ12_CAMRESET);  
  25.   
  26.       
  27.     if (!request_mem_region((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF, CARD_NAME))  
  28.     {  
  29.         ret = -EBUSY;  
  30.         goto error1;  
  31.     }  
  32.   
  33.       
  34.     camif_base_addr = (unsigned long)ioremap_nocache((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF);  
  35.     if (camif_base_addr == (unsigned long)NULL)  
  36.     {  
  37.         ret = -EBUSY;  
  38.         goto error2;  
  39.     }  
  40.   
  41.       
  42.     pdev->clk = clk_get(NULL, "camif");  
  43.     if (IS_ERR(pdev->clk))  
  44.     {  
  45.         ret = -ENOENT;  
  46.         goto error3;  
  47.     }  
  48.     clk_enable(pdev->clk);  
  49.   
  50.     camif_upll_clk = clk_get(NULL, "camif-upll");  
  51.     clk_set_rate(camif_upll_clk, 24000000);  
  52.     mdelay(100);  
  53.   
  54.       
  55.     mutex_init(&pdev->rcmutex);  
  56.     pdev->rc = 0;  
  57.   
  58.       
  59.     pdev->input = 0;  
  60.   
  61.       
  62.     pdev->state = CAMIF_STATE_FREE;  
  63.   
  64.       
  65.     pdev->cmdcode = CAMIF_CMD_NONE;  
  66.     init_waitqueue_head(&pdev->cmdqueue);  
  67.   
  68.       
  69.     if (misc_register(&misc) < 0)  
  70.     {  
  71.         ret = -EBUSY;  
  72.         goto error4;  
  73.     }  
  74.     printk(KERN_ALERT"s3c2440 camif init done/n");  
  75.       
  76. //  sccb_init();  
  77.     CFG_WRITE(SIO_C);  
  78.     CFG_WRITE(SIO_D);  
  79.   
  80.     High(SIO_C);  
  81.     High(SIO_D);  
  82.     WAIT_STABLE();  
  83.   
  84.     hw_reset_camif();  
  85.     has_ov9650 = s3c2440_ov9650_init() >= 0;  
  86.     s3c2410_gpio_setpin(S3C2410_GPG4, 1);  
  87.     return 0;  
  88.   
  89. error4:  
  90.     clk_put(pdev->clk);  
  91. error3:  
  92.     iounmap((void *)camif_base_addr);  
  93. error2:  
  94.     release_mem_region((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF);  
  95.       
  96. error1:  
  97.     return ret;  
  98. }  

①定义Camera Interface的硬件管脚功能(即设置GPJ为功能引脚)

②初始化Camera的虚拟内存地址

 request_mem_region() -- 将起始地址为[start, start+n-1]的资源插入根资源iomem_resource中。参数startI/O内存资源的起始物理地址(CPURAM物理地址空间中的物理地址),参数n指定I/O内存资源的大小。
#define request_mem_region(start, n, name) /
        __request_region(&iomem_resource, (start), (n), (name))
 注调用request_mem_region()不是必须的,但是建议使用。该函数的任务是检查申请的资源是否可用,如果可用则申请成功,并标志为已经使用,其他驱动想再申请该资源时就会失败。
③映射为虚拟内存

ioremap_nocache - 把内存映射到CPU空间  

void __iomem * ioremap_nocache (unsigned long phys_addr, unsigned long size);   

phys_addr 要映射的物理地址  

size 要映射资源的大小  

ioremap_nocache进行一系列平台相关的操作使得CPU可以通过readb/readw/readl/writeb/writew/writelIO函数进行访问。

返回的地址不保证可以作为虚拟地址直接访问。 

[译者按:在译者的使用过程种并没有出现不能作为虚拟地址直接访问的情况,可能是某些平台下的不可以吧。译者的使用平台是x86ixp425]

这个版本的ioremap确保这些内存在CPU是不可缓冲的,如同PCI总线上现存的缓冲规则一样。注:此时在很多总线上仍有其他的缓冲和缓存。在某些特殊的驱动中,作者应当在PCI写的时候进行读取。
这对于一些控制寄存器在这种不希望复合写或者缓冲读的区域内时是非常有用的
返回的映射地址必须使用iounmap来释放。

④初始化Camera的时钟(这里通过一个例子说明函数)

如何获取FCLK, HLCK 和 PCLK的时针频率呢?

可先通过clk_get获取一个clk结构体

 

 

struct clk *clk_get(struct device *dev, const char *id)
struct clk {
    struct list_head list;
    struct module *owner;
    struct clk *parent;
    const char *name; 
    int id;
    int usage;
    unsigned long rate; 
    unsigned long ctrlbit;
    int (*enable)(struct clk *, int enable);
    int (*set_rate)(struct clk *c, unsigned long rate);
    unsigned long (*get_rate)(struct clk *c);
    unsigned long (*round_rate)(struct clk *c, unsigned long rate);
    int (*set_parent)(struct clk *c, struct clk *parent);
};

再将clk_get返回的clk结构体传递给clk_get_rate,获取该时钟的频率

 

unsigned long clk_get_rate(struct clk *clk)

一个例子:

这里出现了另一个时针uclk,专门给usb供给时针信号。uclk是外部时针源,由s3c2410芯片的gph8/uclk管脚引入,给uart提供外部时针信号,以获取更精确地时针频率。

⑤初始化计数器和它的互斥

用互斥锁可以使线程顺序执行。互斥锁通常只允许一个线程执行一个关键部分的
代码,来同步线程。互斥锁也可以用来保护单线程代码。
⑥初始化输入的源图象

⑦初始化Camera 接口的状态

enum

{

CAMIF_STATE_FREE = 0, // not openned

CAMIF_STATE_READY = 1, // openned, but standby

CAMIF_STATE_PREVIEWING = 2, // in previewing

CAMIF_STATE_CODECING = 3 // in capturing

};

⑧初始化命令代码,和初始化等待队列头

enum

{

CAMIF_CMD_NONE = 0,

CAMIF_CMD_SFMT = 1<<0, // source image format changed.

CAMIF_CMD_WND = 1<<1, // window offset changed.

CAMIF_CMD_ZOOM = 1<<2, // zoom picture in/out

CAMIF_CMD_TFMT = 1<<3, // target image format changed.

CAMIF_CMD_P2C = 1<<4, // need camif switches from p-path to c-path

CAMIF_CMD_C2P = 1<<5, // neet camif switches from c-path to p-path

CAMIF_CMD_STOP = 1<<16 // stop capture

};

⑨注册到视频设备层

杂项设备(misc device

杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在 Linux 内核的include/linux目录下有Miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10 ,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的。 

也就是说,misc设备其实也就是特殊的字符设备,可自动生成设备节点。 

字符设备(char device) 

使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时,如果有多个设备使用该函数注册驱动程序,LED_MAJOR不能相同,否则几个设备都无法注册(我已验证)。如果模块使用该方式注册并且 LED_MAJOR0(自动分配主设备号 ),使用insmod命令加载模块时会在终端显示分配的主设备号和次设备号,在/dev目录下建立该节点,比如设备leds,如果加载该模块时分配的主设备号和次设备号为2530,则建立节点:mknod leds c 253 0。使用register_chrdev (LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时都要手动建立节点 ,否则在应用程序无法打开该设备。

这里有一个结构很关键,也是我们程序脉络的一个跟踪点if (misc_register(&misc) < 0)中的misc,我们先把整个加载程序跟踪完,在回过来跟踪这个入口点。

⑩初始化sccb传输协议,与IIC协议相似,并初始化外部摄相设备

首先将GPE14GPE15设置成输入输出功能,将两个引脚都置成高电平,延迟

重起外部摄相硬件设备

初始化外部硬件设备:

打开电源(GPG11设置为输出模式,并给其数据为0

检测设备的厂商ID

显示设备的产品ID

配置OV9650的内部各个寄存器,在配制寄存起开始时down(®s_mutex);在配置结束时up(®s_mutex);

GPG4引脚数据设置为1(这个设置不知道为什么,是开启LCD电源?我把这个语句注释掉了,再编译驱动下载到开发板,用camera_test测试了一下,发现一切正常)

其他的一些出错处理,主要是对前面那些出现错误后的反操作(比如申请内存,如果出错,在出错处理中就要释放内存)。

2、misc的跟踪

 

[c-sharp]  view plain copy print ?
  1. static struct miscdevice misc = {  
  2.         .minor = MISC_DYNAMIC_MINOR,  
  3.         .name = CARD_NAME,  
  4.         .fops = &camif_fops,  
  5. };  
   

 

 

 

 

 

 

 

 

这里#define MISC_DYNAMIC_MINOR 255

#define CARD_NAME  "camera"

关键点是camif_fops,这个结构是字符设备驱动程序的核心,当应用程序操作设备文件时所调用的openreadwrite等函数,最终会调用这个结构体中指定的对应函数

2、file_operations结构体camif_fops

 

 

[c-sharp] view plaincopyprint?
  1. static struct file_operations camif_fops =  
  2. {  
  3.     .owner      = THIS_MODULE,  
  4.     .open       = camif_open,  
  5.     .release        = camif_release,  
  6.     .read       = camif_read,  
  7. };  

 

 

由这个结构体我们知道,程序定义了三个接口函数open,release,read。因此接下来我们要具体的分跟踪这三个函数,这也是编写字符行设备驱动程序时我们主要完成的工作。

 

 camif_open的实现

 

[c-sharp] view plaincopyprint?
  1. static int camif_open(struct inode *inode, struct file *file)  
  2. {  
  3.     struct tq2440_camif_dev *pdev;  
  4.     struct tq2440_camif_fh *fh;  
  5.   
  6.     int ret;  
  7.   
  8.     if (!has_ov9650) {  
  9.         return -ENODEV;  
  10.     }  
  11.     pdev = &camera;  
  12.   
  13.   
  14.     fh = kzalloc(sizeof(*fh),GFP_KERNEL); // alloc memory for filehandle  
  15.     if (NULL == fh)  
  16.     {  
  17.         return -ENOMEM;  
  18.     }  
  19.     fh->dev = pdev;  
  20.   
  21.     pdev->state = CAMIF_STATE_READY;  
  22.   
  23.     init_camif_config(fh);  
  24.   
  25.     ret = init_image_buffer();  // init image buffer.  
  26.     if (ret < 0)  
  27.     {  
  28.         goto error1;  
  29.     }  
  30.   
  31.     request_irq(IRQ_S3C2440_CAM_C, on_camif_irq_c, IRQF_DISABLED, "CAM_C", pdev);   // setup ISRs  
  32.     if (ret < 0)  
  33.     {  
  34.         goto error2;  
  35.     }  
  36.   
  37.     request_irq(IRQ_S3C2440_CAM_P, on_camif_irq_p, IRQF_DISABLED, "CAM_P", pdev);  
  38.     if (ret < 0)  
  39.     {  
  40.         goto error3;  
  41.     }  
  42.   
  43.     clk_enable(pdev->clk);       // and enable camif clock.  
  44.   
  45.     soft_reset_camif();  
  46.   
  47.     file->private_data = fh;  
  48.     fh->dev  = pdev;  
  49.     update_camif_config(fh, 0);  
  50.     return 0;  
  51.   
  52. error3:  
  53.     free_irq(IRQ_S3C2440_CAM_C, pdev);  
  54. error2:  
  55.     free_image_buffer();  
  56. error1:  
  57.     kfree(fh);  
  58.     return ret;  
  59. }  

 

①通过has_ov9650来判断是否检测到0v9650设备的厂商ID(这个变量在程序加载时被设置)

②给设备结构体tq2440_camif_dev在内存中分配区域

③将设备结构体中的状态变量设置为CAMIF_STATE_READY

④初始化camera接口,配置相关的寄存器

⑤初始化图像的缓存区(即4DMA通道的缓存地址)

⑥设置请求C通道,P通道的的中断

 

⑦使能camif的时钟,软件重起camif,将设备指针赋值给文件指针的私有数据

⑧最后更新配置(这步在这里好像也是可有可无的)

1init_camif_config

 

[c-sharp] view plaincopyprint?
  1. static void init_camif_config(struct tq2440_camif_fh * fh)  
  2. {  
  3.     struct tq2440_camif_dev * pdev;  
  4.   
  5.     pdev = fh->dev;  
  6.   
  7.     pdev->input = 0; // FIXME, the default input image format, see inputs[] for detail.  
  8.   
  9.       
  10.     pdev->srcHsize = 1280;   // FIXME, the OV9650's horizontal output pixels.  
  11.     pdev->srcVsize = 1024;   // FIXME, the OV9650's verical output pixels.  
  12.   
  13.       
  14.     pdev->wndHsize = 1280;             
  15.     pdev->wndVsize = 1024;  
  16.   
  17.       
  18.     pdev->coTargetHsize = pdev->wndHsize;  
  19.     pdev->coTargetVsize = pdev->wndVsize;  
  20.   
  21.       
  22.     pdev->preTargetHsize = 320;  
  23.     pdev->preTargetVsize = 240;  
  24.   
  25.     update_camif_config(fh, CAMIF_CMD_STOP);  
  26. }  
  27.   
  28. 这里有个函数update_camif_config(fh,CMAIF_CMD_STOP),用于更新camera接口的配置  
  29.   
  30. static void update_camif_config (struct tq2440_camif_fh * fh, u32 cmdcode)  
  31. {  
  32.     struct tq2440_camif_dev * pdev;  
  33.   
  34.     pdev = fh->dev;  
  35.   
  36.     switch (pdev->state)  
  37.     {  
  38.     case CAMIF_STATE_READY:  
  39.         update_camif_regs(fh->dev);  // config the regs directly.  
  40.         break;  
  41.   
  42.     case CAMIF_STATE_PREVIEWING:  
  43.   
  44.           
  45.   
  46.              disable_irq(IRQ_S3C2440_CAM_P);  // disable cam-preview irq.  
  47.   
  48.           
  49.         if (cmdcode & CAMIF_CMD_SFMT)  
  50.         {  
  51.             // ignore it, nothing to do now.  
  52.         }  
  53.   
  54.           
  55.         if (cmdcode & CAMIF_CMD_TFMT)  
  56.         {  
  57.                   
  58.                 pdev->cmdcode |= CAMIF_CMD_TFMT;  
  59.         }  
  60.   
  61.           
  62.         if (cmdcode & CAMIF_CMD_WND)  
  63.         {  
  64.             pdev->cmdcode |= CAMIF_CMD_WND;  
  65.         }  
  66.   
  67.           
  68.         if (cmdcode & CAMIF_CMD_ZOOM)  
  69.         {  
  70.             pdev->cmdcode |= CAMIF_CMD_ZOOM;  
  71.         }  
  72.   
  73.           
  74.         if (cmdcode & CAMIF_CMD_STOP)  
  75.         {  
  76.             pdev->cmdcode |= CAMIF_CMD_STOP;  
  77.         }  
  78.         enable_irq(IRQ_S3C2440_CAM_P);  // enable cam-preview irq.  
  79.   
  80.         wait_event(pdev->cmdqueue, (pdev->cmdcode==CAMIF_CMD_NONE));  // wait until the ISR completes command.  
  81.         break;  
  82.   
  83.     case CAMIF_STATE_CODECING:  
  84.   
  85.           
  86.   
  87.         disable_irq(IRQ_S3C2440_CAM_C);     // disable cam-codec irq.  
  88.   
  89.           
  90.         if (cmdcode & CAMIF_CMD_SFMT)  
  91.         {  
  92.             // ignore it, nothing to do now.  
  93.         }  
  94.   
  95.           
  96.         if (cmdcode & CAMIF_CMD_TFMT)  
  97.         {  
  98.                   
  99.                 pdev->cmdcode |= CAMIF_CMD_TFMT;  
  100.         }  
  101.   
  102.           
  103.         if (cmdcode & CAMIF_CMD_WND)  
  104.         {  
  105.             pdev->cmdcode |= CAMIF_CMD_WND;  
  106.         }  
  107.   
  108.           
  109.         if (cmdcode & CAMIF_CMD_ZOOM)  
  110.         {  
  111.             pdev->cmdcode |= CAMIF_CMD_ZOOM;  
  112.         }  
  113.   
  114.           
  115.         if (cmdcode & CAMIF_CMD_STOP)  
  116.         {  
  117.             pdev->cmdcode |= CAMIF_CMD_STOP;  
  118.         }  
  119.         enable_irq(IRQ_S3C2440_CAM_C);  // enable cam-codec irq.  
  120.         wait_event(pdev->cmdqueue, (pdev->cmdcode==CAMIF_CMD_NONE));  // wait until the ISR completes command.  
  121.         break;  
  122.   
  123.     default:  
  124.         break;  
  125.     }  
  126. }  
  127.   
  128.   
  129. update_camif_regs(fh->dev);表示如果为准备状态就直接配置寄存器  
  130.   
  131. static void __inline__ update_camif_regs(struct tq2440_camif_dev * pdev)  
  132. {  
  133.     if (!in_irq())  
  134.     {  
  135.         while(1)    // wait until VSYNC is 'L'  
  136.         {  
  137.             barrier();  
  138.             if ((ioread32(S3C244X_CICOSTATUS)&(1<<28)) == 0)  
  139.                 break;  
  140.         }  
  141.     }  
  142.   
  143.       
  144.     update_source_fmt_regs(pdev);  
  145.     update_target_wnd_regs(pdev);  
  146.     update_target_fmt_regs(pdev);  
  147.     update_target_zoom_regs(pdev);  
  148. }  
  149.   
  150. 这里最后4个函数是配置camera interface寄存器的函数,由于寄存器的配置过程跟裸机的配置大同小异,所以这里就不进入其中讲解了,但有一点要注意,如果要修改显示效果,我觉得可以到这里面来配置一下寄存器,修改一下参数  
  151. init_camif_buffer  
  152.   
  153.   
  154. static int __inline__ init_image_buffer(void)  
  155. {  
  156.     int size1, size2;  
  157.     unsigned long size;  
  158.     unsigned int order;  
  159.   
  160.       
  161.     size1 = MAX_C_WIDTH * MAX_C_HEIGHT * 16 / 8;  
  162.   
  163.       
  164.     size2 = MAX_P_WIDTH * MAX_P_HEIGHT * 16 / 8;  
  165.   
  166.     size = (size1 > size2)?size1:size2;  
  167.   
  168.     order = get_order(size);  
  169.     img_buff[0].order = order;  
  170.     img_buff[0].virt_base = __get_free_pages(GFP_KERNEL|GFP_DMA, img_buff[0].order);  
  171.     if (img_buff[0].virt_base == (unsigned long)NULL)  
  172.     {  
  173.         goto error0;  
  174.     }  
  175.     img_buff[0].phy_base = img_buff[0].virt_base - PAGE_OFFSET + PHYS_OFFSET;   // the DMA address.  
  176.   
  177.     img_buff[1].order = order;  
  178.     img_buff[1].virt_base = __get_free_pages(GFP_KERNEL|GFP_DMA, img_buff[1].order);  
  179.     if (img_buff[1].virt_base == (unsigned long)NULL)  
  180.     {  
  181.         goto error1;  
  182.     }  
  183.     img_buff[1].phy_base = img_buff[1].virt_base - PAGE_OFFSET + PHYS_OFFSET;   // the DMA address.  
  184.   
  185.     img_buff[2].order = order;  
  186.     img_buff[2].virt_base = __get_free_pages(GFP_KERNEL|GFP_DMA, img_buff[2].order);  
  187.     if (img_buff[2].virt_base == (unsigned long)NULL)  
  188.     {  
  189.         goto error2;  
  190.     }  
  191.     img_buff[2].phy_base = img_buff[2].virt_base - PAGE_OFFSET + PHYS_OFFSET;   // the DMA address.  
  192.   
  193.     img_buff[3].order = order;  
  194.     img_buff[3].virt_base = __get_free_pages(GFP_KERNEL|GFP_DMA, img_buff[3].order);  
  195.     if (img_buff[3].virt_base == (unsigned long)NULL)  
  196.     {  
  197.         goto error3;  
  198.     }  
  199.     img_buff[3].phy_base = img_buff[3].virt_base - PAGE_OFFSET + PHYS_OFFSET;   // the DMA address.  
  200.   
  201.     invalid_image_buffer();  
  202.   
  203.     return 0;  
  204. error3:  
  205.     free_pages(img_buff[2].virt_base, order);  
  206.     img_buff[2].phy_base = (unsigned long)NULL;  
  207. error2:  
  208.     free_pages(img_buff[1].virt_base, order);  
  209.     img_buff[1].phy_base = (unsigned long)NULL;  
  210. error1:  
  211.     free_pages(img_buff[0].virt_base, order);  
  212.     img_buff[0].phy_base = (unsigned long)NULL;  
  213. error0:  
  214.     return -ENOMEM;  
  215. }  
  216.   
  217. (二) camif_read的实现  
  218.   
  219. static ssize_t camif_read(struct file *file, char __user *data, size_t count, loff_t *ppos)  
  220. {  
  221.     int i;  
  222.     struct tq2440_camif_fh * fh;  
  223.     struct tq2440_camif_dev * pdev;  
  224.   
  225.     fh = file->private_data;  
  226.     pdev = fh->dev;  
  227.   
  228.   
  229.     if (start_capture(pdev, 0) != 0)  
  230.     {  
  231.         return -ERESTARTSYS;  
  232.     }  
  233.   
  234.     disable_irq(IRQ_S3C2440_CAM_C);  
  235.     disable_irq(IRQ_S3C2440_CAM_P);  
  236.     for (i = 0; i < 4; i++)  
  237.     {  
  238.         if (img_buff[i].state != CAMIF_BUFF_INVALID)  
  239.         {  
  240.             copy_to_user(data, (void *)img_buff[i].virt_base, count);  
  241.             img_buff[i].state = CAMIF_BUFF_INVALID;  
  242.         }  
  243.     }  
  244.     enable_irq(IRQ_S3C2440_CAM_P);  
  245.     enable_irq(IRQ_S3C2440_CAM_C);  
  246.   
  247.   
  248.     return count;  

 

①开始图像捕捉,参数表示是捕捉视频流还是仅仅捕捉一张图片

 

②关闭P通道和C通道的中断

③通过一个for循环语句判断数据存放地址的状态,标志为有效,则把数据从内核复制到用户空间,并将标志设置为无效

④使能P通道和C通道的中断

(1)start_capture

 

 

[c-sharp] view plaincopyprint?
  1.   
  2.   
  3. static int start_capture(struct tq2440_camif_dev * pdev, int stream)  
  4. {  
  5.     int ret;  
  6.   
  7.     u32 ciwdofst;  
  8.     u32 ciprscctrl;  
  9.     u32 ciimgcpt;  
  10.   
  11.     ciwdofst = ioread32(S3C244X_CIWDOFST);  
  12.     ciwdofst    |= (1<<30)    // Clear the overflow indication flag of input CODEC FIFO Y  
  13.         |(1<<15)      // Clear the overflow indication flag of input CODEC FIFO Cb  
  14.         |(1<<14)      // Clear the overflow indication flag of input CODEC FIFO Cr  
  15.         |(1<<13)      // Clear the overflow indication flag of input PREVIEW FIFO Cb  
  16.         |(1<<12);     // Clear the overflow indication flag of input PREVIEW FIFO Cr  
  17.     iowrite32(ciwdofst, S3C244X_CIWDOFST);  
  18.   
  19.     ciprscctrl = ioread32(S3C244X_CIPRSCCTRL);  
  20.     ciprscctrl |= 1<<15;  // preview scaler start  
  21.     iowrite32(ciprscctrl, S3C244X_CIPRSCCTRL);  
  22.   
  23.     pdev->state = CAMIF_STATE_PREVIEWING;  
  24.   
  25.     ciimgcpt = (1<<31)        // camera interface global capture enable  
  26.          |(1<<29);        // capture enable for preview scaler.  
  27.     iowrite32(ciimgcpt, S3C244X_CIIMGCPT);  
  28.   
  29.     ret = 0;  
  30.     if (stream == 0)  
  31.     {  
  32.         pdev->cmdcode = CAMIF_CMD_STOP;  
  33.   
  34.         ret = wait_event_interruptible(pdev->cmdqueue, pdev->cmdcode == CAMIF_CMD_NONE);  
  35.     }  
  36.     return ret;  
  37. }  
  38.   
  39. ⑴配置窗口移位寄存器,清空各个FIFO  
  40. ⑵配置预览通道主框控制寄存器,预览模式开始  
  41. ⑶将设备接口体的状态标志标识为CAMIF_STATE_PREVIEWING  
  42. ⑷配置图像捕捉使能寄存器,图像捕捉全局使能,预览通道捕捉使能  
  43. ⑸一个判断语句,如果stream为0,则设备结构体的命令变量变为CAMIF_CMD_STOP,并设置一个等待中断队列  
  44. camif_release的实现  
  45.   
  46. static int camif_release(struct inode *inode, struct file *file)  
  47. {  
  48.     struct tq2440_camif_fh  * fh;  
  49.     struct tq2440_camif_dev * pdev;  
  50.   
  51.     fh = file->private_data;  
  52.     pdev = fh->dev;  
  53.   
  54.   
  55.         clk_disable(pdev->clk);          // stop camif clock  
  56.   
  57.         free_irq(IRQ_S3C2440_CAM_P, pdev);  // free camif IRQs  
  58.   
  59.         free_irq(IRQ_S3C2440_CAM_C, pdev);  
  60.   
  61.         free_image_buffer();            // and free image buffer  
  62.   
  63.     return 0;  
  64. }  
  65.   
  66. ①关闭时钟,释放P通道中断,释放C通道中断,释放4个DMA缓存  

5、卸载函数camif_cleanup

相对与所有的程序块,这部分应该是最简单的,基本就是释放一些资源,前面的一些反操作

 

 

 

static void __exit camif_cleanup(void)

{

struct tq2440_camif_dev *pdev;

 

// sccb_cleanup();

CFG_READ(SIO_C);

CFG_READ(SIO_D);

 

pdev = &camera;

 

misc_deregister(&misc);

 

 

clk_put(pdev->clk);

 

iounmap((void *)camif_base_addr);

release_mem_region((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF);

 

printk(KERN_ALERT"tq2440_camif: module removed/n");

}

 

 

6、总结

由上面的程序跟踪我们发现字符设备程序的主体就是file_operation的填充,上面很多内嵌的函数都没有粘贴出来,我不敢说不重要,实际是很多程序嵌来嵌去的很容易把我整蒙,所以只是大致的读了一意思(能明白这块是做什么的以及如果我要修改程序我应该明白修改什么地方就行)。


你可能感兴趣的:(基于2.6.35内核的OV9650摄像头驱动移植)