Mini2440 之Linux 移植开发实战指南让梦想从实践开始

221
Mini2440 Linux 移植开发实战指南 让梦想从实践开始 2010-4-14 本手册由广州友善之臂计算机科技有限公司(简称“友善之臂” )创建和维护,并作为标准 用户手册的一个补充,仅供嵌入式爱好者学习参考使用,友善之臂目前并不对本手册的内容 提供任何解释和解答服务,用户可以在论坛中反馈你所遇到的问题和疑问,我们将在以后的 更新中修正或者采纳您的建议,本手册主要以首页日期为版本标志。 本手册主要介绍了 Linux-2.6.32.2 内核的详细移植过程,它包含了友善之臂提供的几乎所 有驱动程序的移植或编写、测试等;本手册还详细介绍了 mini2440 所用文件系统的构建过程。 Mini2440 开发板凭借优良的质量、齐全实用的软件、精巧细致的设计以及快速有效的技 术支持,深得用户的喜爱和认同,是国内使用人数最多的 ARM9 开发板,并且已经被 Linux 官方内核所支持(Linux-2.6.31 开始),有很多网友基于 Mini2440 学习和开发嵌入式软件, 并写了很多有价值的文档和经验总结,本手册的部分内容就来源于此,在此我们十分感谢各 位爱好者的分享和贡献,本手册所参考的文章力求注明原文出处,如有不实,请作者和我们 及时联系以便修正。 因为 micro2440 mini2440 的硬件接口和资源都是完全相同的,所以本手册也可以适用 micro2440 开发板。 我们欢迎各位网友复制传播本手册,友善之臂仅保留本手册的解释和修改权。 友善之臂公司网址:http://www.arm9.net 本手册由ARM9 之家论坛(http://www.arm9home.net )发布,转载请注明出处。

Transcript of Mini2440 之Linux 移植开发实战指南让梦想从实践开始

Mini2440 之 Linux 移植开发实战指南

让梦想从实践开始

2010-4-14

简 介 本手册由广州友善之臂计算机科技有限公司(简称“友善之臂”)创建和维护,并作为标准

用户手册的一个补充,仅供嵌入式爱好者学习参考使用,友善之臂目前并不对本手册的内容

提供任何解释和解答服务,用户可以在论坛中反馈你所遇到的问题和疑问,我们将在以后的

更新中修正或者采纳您的建议,本手册主要以首页日期为版本标志。 本手册主要介绍了 Linux-2.6.32.2 内核的详细移植过程,它包含了友善之臂提供的几乎所

有驱动程序的移植或编写、测试等;本手册还详细介绍了 mini2440 所用文件系统的构建过程。

Mini2440 开发板凭借优良的质量、齐全实用的软件、精巧细致的设计以及快速有效的技

术支持,深得用户的喜爱和认同,是国内使用人数 多的 ARM9 开发板,并且已经被 Linux官方内核所支持(从 Linux-2.6.31 开始),有很多网友基于 Mini2440 学习和开发嵌入式软件,

并写了很多有价值的文档和经验总结,本手册的部分内容就来源于此,在此我们十分感谢各

位爱好者的分享和贡献,本手册所参考的文章力求注明原文出处,如有不实,请作者和我们

及时联系以便修正。 因为 micro2440 和 mini2440 的硬件接口和资源都是完全相同的,所以本手册也可以适用

于 micro2440 开发板。 我们欢迎各位网友复制传播本手册,友善之臂仅保留本手册的解释和修改权。

友善之臂公司网址:http://www.arm9.net 本手册由ARM9 之家论坛(http://www.arm9home.net)发布,转载请注明出处。

目 录

目 录 ........................................................................................................................................................................... 2 第一章Git的安装和使用(基于Fedora 9).................................................................................................................... 6 

1.1 下载Git源代码 ........................................................................................................................................... 6 1.2 安装Git ....................................................................................................................................................... 6 1.3 检查版本号 ................................................................................................................................................. 6 

第二章 关于supervivi和开发环境............................................................................................................................. 8 2.1 关于supervivi ............................................................................................................................................... 8 

2.1.1 新的supervivi及分区表................................................................................................................. 8 2.1.2 如何识别开发板上supervivi版本..................................................................................................... 8 2.1.3 如何恢复或者更新开发板的supervivi............................................................................................ 10 2.1.4 supervivi功能一览表........................................................................................................................ 10 2.1.5 如何使用其他开源的bootloader .................................................................................................... 12 2.1.6  为什么要使用 新的supervivi................................................................................................. 13 

2.2 关于开发平台 ............................................................................................................................................ 13 2.3 关于交叉编译器 ......................................................................................................................................... 14 

第三章 Linux-2.6.32.2 内核移植详细步骤............................................................................................................. 16 3.1 引子 ............................................................................................................................................................ 16 3.2 获取Linux内核源代码............................................................................................................................... 16 3.3 解压内核源代码 ........................................................................................................................................ 17 3.4 指定交叉编译变量 .................................................................................................................................... 18 3.5 克隆建立自己的目标平台 ........................................................................................................................ 19 

3.5.1 关于机器码 ..................................................................................................................................... 19 3.5.2 修改时钟源频率 ............................................................................................................................. 22 3.5.3 从SMDK2440 到MINI2440............................................................................................................ 23 3.5.4 编译测试 ......................................................................................................................................... 24 

3.6 关于内核配置菜单中的mini2440 选项 .................................................................................................... 28 3.7 移植Nand驱动并更改分区信息................................................................................................................ 31 

3.7.1 Linux-2.6.32.2 内核所支持的Nand Flash类型 ............................................................................... 31 3.7.2 修改Nand Flash分区表 ................................................................................................................... 32 3.7.3 从启动信息中查看分区表 ............................................................................................................. 36 

3.8 移植yaffs2 .................................................................................................................................................. 38 3.8.1 获取yaffs2 源代码 .......................................................................................................................... 38 3.8.2 为内核打上yaffs2 补丁................................................................................................................... 39 3.8.3 配置和编译带YAFFS2 支持的内核............................................................................................... 41 3.8.4 烧写到开发板运行测试 ................................................................................................................. 43 

3.9 移植DM9000 网卡驱动 ............................................................................................................................. 43 3.9.1 设备资源初始化 ............................................................................................................................. 43 3.9.2 调整DM9000 所用的位宽寄存器 .................................................................................................. 45 3.9.3 关于MAC地址 ................................................................................................................................ 46 3.9.4 配置内核加入DM9000,并编译运行测试 ................................................................................... 47 

3.10 激活RTC驱动........................................................................................................................................... 48 3.10.1 在初始化文件中加入RTC设备结构 ............................................................................................ 48 3.10.2 在内核中配置RTC........................................................................................................................ 49 3.10.3 测试RTC........................................................................................................................................ 49 

3.11 添加LCD背光驱动(带详细注解) ............................................................................................................ 51 3.11.1 LCD背光控制原理......................................................................................................................... 51 3.11.2 在内核中添加背光驱动程序........................................................................................................ 52 

3.12 移植LCD显示驱动 .................................................................................................................................. 57 3.12.1 LCD驱动基础知识 ........................................................................................................................ 57 3.12.2 新内核中的pixclock参数.............................................................................................................. 59 3.12.3 在内核中添加各种LCD类型的支持............................................................................................ 61 3.12.4 配置内核并下载到开发板测试.................................................................................................... 67 

3.13 修改Linux Logo ....................................................................................................................................... 68 3.13.1 使用命令行工具修改Linux LOGO.............................................................................................. 68 3.13.2 使用图形化的LogoMaker制作Linux LOGO ............................................................................... 69 

3.14 添加ADC驱动 .......................................................................................................................................... 74 3.14.1 关于S3C2440 的ADC和触摸屏接口 ........................................................................................... 74 3.14.2 在内核中添加ADC驱动 ............................................................................................................... 74 3.14.3 ADC测试程序 ................................................................................................................................ 82 

3.15 添加触摸屏驱动(带详细原理分析)........................................................................................................ 84 3.15.1 在内核中添加触摸屏驱动程序.................................................................................................... 84 3.15.2 配置编译内核并测试触摸屏驱动................................................................................................ 92 3.15.3 触摸屏驱动原理详解 ................................................................................................................... 93 

3.16 配置USB外设......................................................................................................................................... 102 3.16.1 配置和测试USB键盘、扫描器和鼠标...................................................................................... 102 3.16.2 测试USB键盘、扫描器和鼠标.................................................................................................. 103 3.16.3 配置优盘 ...................................................................................................................................... 104 3.16.4 测试优盘 ..................................................................................................................................... 107 3.16.5 配置和测试USB摄像头.............................................................................................................. 109 3.16.6 测试USB摄像头...........................................................................................................................112 3.16.7 配置和测试USB无线网卡...........................................................................................................114 3.16.8 测试USB无线网卡.......................................................................................................................118 3.16.9 配置USB转串口...........................................................................................................................118 3.16.10 测试USB转串口.........................................................................................................................118 

3.17 移植SD卡驱动 ........................................................................................................................................118 

3.17.1 在内核中注册SD设备驱动 .........................................................................................................118 3.17.2 测试SD卡 .....................................................................................................................................119 3.17.3 mini2440 的SD卡驱动分析(来自网络) ...................................................................................... 121 1.硬件基础 .............................................................................................................................................. 121 2.MMC子系统的基本框架结构............................................................................................................. 121 3.HOST层分析 ........................................................................................................................................ 122 4.CORE层分析........................................................................................................................................ 127 5. CARD层分析 ...................................................................................................................................... 130 6. 实验 .................................................................................................................................................... 135 7 结论 ...................................................................................................................................................... 135 

3.18 移植UDA1341 音频驱动....................................................................................................................... 136 3.18.1 在初始化文件中加入UDA1341 设备结构 ................................................................................ 136 3.18.2 在内核中配置UDA1341 设备驱动 ............................................................................................ 137 3.18.3 mp3 放音测试 .............................................................................................................................. 139 3.18.4 修正驱动中的录音代码 ............................................................................................................. 140 3.18.5 录音测试 ..................................................................................................................................... 141 

3.19 修整串口驱动 ........................................................................................................................................ 143 3.19.1 把UART2 改为普通串口驱动 .................................................................................................... 143 3.19.2 测试串口 ..................................................................................................................................... 144 

3.20 移植I2C-EEPROM驱动 ......................................................................................................................... 147 3.20.1 在内核中配置I2C驱动 ............................................................................................................... 147 3.20.2 测试I2C-EEPROM...................................................................................................................... 148 

3.21 移植看门狗驱动 .................................................................................................................................... 150 3.21.1 在内核中配置看门狗驱动 ......................................................................................................... 150 3.21.2 关于打开和关闭看门狗 ............................................................................................................. 151 3.21.3 测试看门狗 ................................................................................................................................. 152 

3.22 简单的LED驱动................................................................................................................................. 153 3.22.1 LED驱动原理及编写................................................................................................................... 153 3.22.2 配置编译新内核并测试LED...................................................................................................... 160 3.22.3 测试LED...................................................................................................................................... 161 

3.23 基于中断的按键驱动程序 .................................................................................................................... 163 3.23.1 硬件原理 ..................................................................................................................................... 163 3.23.2 驱动程序分析及编写 ................................................................................................................. 164 3.23.3 把按键驱动加入内核 ................................................................................................................. 170 3.23.4 配置编译新内核 ......................................................................................................................... 171 3.23.5 测试按键 ..................................................................................................................................... 172 

3.24 添加PWM控制蜂鸣器驱动 ................................................................................................................... 174 3.24.1 硬件解析 ..................................................................................................................................... 174 3.24.2 编写添加驱动程序 ..................................................................................................................... 176 3.24.3 把驱动程序加入内核 ................................................................................................................. 181 

3.24.4 配置编译新内核 ......................................................................................................................... 182 3.24.5 测试PWM控制蜂鸣器 ................................................................................................................ 182 

第四章 关于文件系统 ........................................................................................................................................... 187 4.1 友善之臂mini2440 root_qtopia 文件系统启动过程分析 ...................................................................... 187 4.2 使用Busybox构建文件系统 ................................................................................................................... 205 

4.2.1 下载busybox源代码 ...................................................................................................................... 205 4.2.2 根文件系统目录说明 .................................................................................................................... 205 4.2.3 建立根文件系统目录 .................................................................................................................... 206 4.2.4 建立动态链接库 ........................................................................................................................... 207 4.2.5 交叉编译Bosybox ......................................................................................................................... 207 4.2.6 建立etc目录下的配置文件............................................................................................................ 209 4.2.7 制作根文件系统映像文件 ............................................................................................................ 210 

4.3 mdev的使用方法和原理 .......................................................................................................................... 214 4.3.1 mdev的使用 ................................................................................................................................... 215 4.3.2 mdev的原理 ................................................................................................................................... 216 4.3.3 一个使用mdev的gpio控制驱动示例............................................................................................ 216 

4.4 移植madplay并加入文件系统................................................................................... 错误!未定义书签。 

4.5 移植web服务器(boa)并加入文件系统 ..................................................................... 错误!未定义书签。 

4.6 移植ftp服务器(vsftp)并加入文件系统 ..................................................................... 错误!未定义书签。 

4.7 移植Qtopia-2.2.0 并加入文件系统 ........................................................................... 错误!未定义书签。 

附录 1 基于mini2440 的开源项目 .......................................................................................... 错误!未定义书签。 

1.1  Linux内核.............................................................................................................. 错误!未定义书签。 

1.2 U-Boot ......................................................................................................................... 错误!未定义书签。 

1.3 QEMU模拟器 ............................................................................................................. 错误!未定义书签。 

1.4 国产抢占式实时开源操作系统RT-Thread ............................................................... 错误!未定义书签。 

1.5 OpenEmbedded............................................................................................................ 错误!未定义书签。 

1.6 Emdebian ..................................................................................................................... 错误!未定义书签。 

1.7 RockBox ...................................................................................................................... 错误!未定义书签。 

1.8 NetBSD........................................................................................................................ 错误!未定义书签。 

1.9 kasim的git ................................................................................................................... 错误!未定义书签。 

1.10 android ....................................................................................................................... 错误!未定义书签。 

1.11 openwrt ...................................................................................................................... 错误!未定义书签。 

附录 2 mini2440 使用经验文集(仅限Linux系统) ................................................................ 错误!未定义书签。 

2.1 把mini2440 作为USB网卡或者移动存储设备(U盘).......................................... 错误!未定义书签。 

2.2 成功移植DHCP客户端到mini2440 .......................................................................... 错误!未定义书签。 

第一章 Git 的安装和使用(基于 Fedora 9)

Git 是用于 Linux 内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持,使源代码的发布和交流极其

方便。 Git 的速度很快,这对于诸如 Linux kernel 这样的大项目来说自然很重要。 Git 为

出色的是它的合并跟踪(merge tracing)能力。 实际上内核开发团队决定开始开发和使用 Git 来作为内核开发的版本控制系统的时

候,世界开源社群的反对声音不少, 大的理由是 Git 太艰涩难懂,从 Git 的内部工作机制

来说,的确是这样。但是随着开发的深入,Git 的正常使用都由一些友好的脚本命令来执行,

使 Git 变得非常好用,即使是用来管理我们自己的开发项目,Git 都是一个友好,有力的工具。

现在,越来越多的著名项目采用 Git 来管理项目开发,例如:wine, U-boot 等,详情看 http://www.kernel.org/git。

- 以上描述来自互联网 目前大多数开源软件均使用 Git 作为版本控制工具,本手册中提到的部分软件就是通

过 git 工具获取的,为了更好的使用这些开源的网络资源,我们务必要先在开发平台(Fedora9)上安装 Git 工具,在此主要使用它来下载现成的代码。

1.1 下载 Git 源代码

下载地址:http://www.kernel.org/pub/software/scm/git/ 在本手册编写时, 新版本为 1.6.6

1.2 安装 Git

(1) #tar xvzf git-1.6.6.tar.gz (2) #cd git-1.6.6 (3) #./configure --prefix=/usr/local 说明:配置安装路径,git 将会安装到/usr/local 目录下 (4) #make (5) #make install

1.3 检查版本号

#git –version

Administrator
铅笔
Administrator
在文本上注释
The stupid content tracker, 傻瓜内容跟踪器。
Administrator
铅笔
Administrator
铅笔
Administrator
铅笔

如图,说明 git 工具已经成功安装

第二章 关于 supervivi 和开发环境

2.1 关于 supervivi

2.1.1 新的 supervivi 及分区表

Supervivi 是友善之臂基于 vivi 改进的一个简单易用的 bootloader,它十分适合初学者下

载和烧写系统使用,也适合产品批量生产使用,Supervivi 由友善之臂维护和升级,目前 supervivi并不提供源代码。

我们建议用户使用 新版本的supervivi,以便配合使用 新的系统软件。你可以在这里

下载到 新的版本:http://www.arm9.net/download.asp 目前 新版是:0945,它代表 2009 年第 45 周,通过它可以烧写 Linux-2.6.32.2 以后的

软件,也可以烧写我们 2010 年更新提供的 WindowsCE5/6 内核文件,一般是 NK.bin supervivi 其实对 Linux 系统是有分区控制的,Supervivi-0945-2K 的分区信息如下:

对于 Linux 系统 此分区表对应于内核 arch/arm/mach-s3c2440/mach-mini2440.c 中 nand flash 的分区表信息 偏移(16 进制) 大小(16 进制) 存储的内容说明 0 40000 Supervivi/vboot/u-boot 等 40000 20000 Linux 启动参数 60000 500000 Kernel,这里为 5M 560000 rest 一般为 root 分区 对于 WindowsCE5/6 系统 偏移(16 进制) 大小(16 进制) 存储的内容说明 0 40000 Supervivi/nboot 等 40000 20000 CE 参数 60000 200000 BootLogo,这里为 2M, 大可以支持 24 位

1024x768 真彩图片 260000 rest

2.1.2 如何识别开发板上 supervivi 版本

Supervivi 在出厂时已经预装入 NOR Flash,把 mini2440 开发板的 S2 开关拨到 NOR 一

侧启动,如下图说明,上电开机,此时板子上的 LED1 会不停的闪烁,同时串口会输出如下信

息,这说明 supervivi 已经正常运行。

注意:supervivi 模式下,LCD 没有任何输出,这是因为 supervivi 无法自动识别开发板

所接的 LCD 类型,因此没有对 LCD 作任何初始化,LCD 自然就没有输出了。 另外,Supervivi 也可以被烧写到 NAND Flash 中运行,启动时按下开发板上的 K1-K6

任意按键均可以进入菜单模式。

此时串口输出如图:

从 Supervivi 的串口输出信息[i]选项中,可以看到 supervivi 的版本为:0945-2K,0945

代表年周日期,2K 代表适合于 2K 页的 Nand Flash,它适用于我们公司出品的 128M/256M/1GB Nand Flash 版本;如果版本信息为:0945-12,则表示适合 512 byte 页面大小的 Nand Flash,它

适用于我们公司出品的 64M Nand Flash 版本。

2.1.3 如何恢复或者更新开发板的 supervivi

如果你不小心破坏了 NOR Flash 中的 supervivi,或者你打算升级,可以按照用户手册附

录 2 的步骤恢复,详细步骤请参考用户手册附录 2 注意:必须要更新 NOR 里面的 supervivi 才算真正更新,通过 NOR 里面的 supervivi 才

能顺利烧写 新软件到 nand flash。

2.1.4 supervivi 功能一览表

注意:以下通过 USB 下载的功能均配合 DNW 这个程序使用。

功能[x]:对 Nand Flash 进行默认分区,此命令仅对 Linux 系统有效。 功能[v]:通过 USB 下载 Linux bootloader 到 Nand Flash 的 bootloader 分区 功能[k]:通过 USB 下载 Linux 内核到 Nand Flash 的 kernel 分区 功能[y]:通过 USB 下载 yaffs 文件系统映象到 Nand Flash 的 root 分区 功能[a]:通过 USB 下载用户程序到 Nand Flash 中,一般这样的用户程序为 bin 可执行

文件,如 2440test(需要支持超过 4K 限制)、uCos2(开发板中带的 uCos2 支持 nand flash 启动)、U-Boot 等;当然也可以是其他任意大小的 bin 程序。

功能[n]:通过 USB 下载 WinCE 之启动程序 Nboot 到 Nand Flash 的 Block0 功能[l]:通过 USB 下载 WinCE 启动时的开机 Logo(bmp 格式的图片) 功能[w]:通过 USB 下载 WinCE 发行映象 NK.bin 到 Nand Flash 功能[d]:通过 USB 下载程序到指定内存地址(通过 DNW 的 Configuration->Option 选项

指定运行地址),并运行。对于本开发板,SDRAM 的物理起始地址是 0x30000000,结束地址

是 0x34000000,大小为 64Mbytes,另外 BIOS 本身占用了 0x33DE8000 以上的空间,因此在

用 BIOS 的 USB 下载功能时应指定地址在 0x30000000 - 0x33DE8000 之间。 功能[z]: 通过 USB 下载 Linux 内核映像文件 zImage 到内存中,下载地址为 0x30008000。 功能[g]: 运行内存中的 Linux 内核映像,该功能一般配合功能[z]一起使用。 功能[f]:擦除 Nand Flash,执行此功能将会擦除整片 Nand Flash 中的数据。(如果您是

第一次使用本开发板,请不必担心误操作,您可以根据用户手册第三章的步骤恢复到出厂状态) 功能[b]:启动系统,如果烧入了 linux 或者 wince,执行从命令将自动辨认识别启动系

统。 功能[s]:设置 linux 启动参数,详细见子菜单说明 功能[u]:备份整个 Nand Flash 中的内容,通过 USB 上传到 PC 存储为一个文件,该功

能类似于 PC 系统中经常用的 Ghost 工具。 功能[r]:使用备份出来的文件恢复到 Nand Flash 功能[i]:版本信息 功能[q]:返回 vivi 的命令交互模式,如图

在交互模式下输入 menu 命令,则可以返回到菜单模式。

2.1.5 如何使用其他开源的 bootloader

因为 S3C2440 可以支持 NAND Flash 启动,如果把开发板的 S2 开关拨到 NAND 一侧,

这时就相当于完全摘除了 NOR Flash,这也是为什么有的开发板只提供了 Nand Flash 的原因。

但为了开发方便,我们特意保留了 NOR Flash,把不经常更改的 Bootloader 烧写到 NOR Flash中,并借助它来管理 Nand Flash(主要是烧写或者备份)可以大大提升开发的效率。因为开发中

难免会不小心破坏了 Nand Flash 里面的全部内容,这就不得不借助 JTAG 工具来恢复 Nand Flash 里面的 Bootloader,一般 JTAG 操作的步骤是很麻烦的,而且速度也不快。而通过 NOR Flash 里面的 bootloader,则可以根据自己的意图把它设计得很强大,在这里,我们的 supervivi主要是利用了 USB 快速下载的特性,实现了对 Nand Flash 的各种简洁的烧写功能。

根据上面的 supervivi 功能一览表,我们可以知道,使用其中的[a]功能,就可以方便的

把其他 bootloader 烧写到 Nand Flash 的起始地址 Block 0,如果其他的 bootloader 并不大,比如

Administrator
铅笔
Administrator
铅笔

一般常见的开源 U-boot,或者我们提供的 vboot 或者 nboot 等,也可以使用[v]功能项来烧写,

它们的结果都是一样的:把程序烧写到 Nand Flash 的 Block 0 起始的位置。

2.1.6 为什么要使用 新的 supervivi

(1) 新版的 Supervivi可以支持更大的 Linux 内核分区,为 5M (2) 新的 supervivi 支持更大的 WindowsCE 启动图片, 大可支持分辨率为 1024x768 分

辨率的 24 位真彩图片,或者 1280x800分辨率的 16位真彩图片。 (3) 使用 新的 supervivi 可以烧写我们 新的WindowsCE5/6系统,如果你烧写完之后系

统仅运行了一次,而从 nand启动时停止在开机画面处,这一般是因为你使用了老版

本的 supervivi 所致。 (4) 新的 supervivi 可以支持更多的 Nand Flash类型 supervivi‐64M适用于 64M nand flash版本的开发板,具体的 nand flash型号可为: 

‐ HY27US08121  韩国现代公司出品 ‐ K9F1208  韩国三星公司出品 

Supervivi‐128M适用于 128M/256M/512M/1Gb nand flash版本的开发板,具体的型号可

为: ‐ K9F1G08: 128M ‐ K9F2G08 : 256M ‐ K9F4G08: 512M ‐ K9K8G08: 1Gb

2.2 关于开发平台

我们自从 2009-4-29 推出了 Linux-2.6.29(现在是 Linux-2.6.32.2)+Qtopia-2.2.0 新

系统, 它具有更好的特性和功能,并使用符合EABI标准的新型编译器: arm-linux-gcc-4.3.2。

对于Linux-2.6.29+Qtopia-2.2.0,我们是基于Fedora9 平台做开发的, 所有的配置和

编译脚本也基于此平台,我们没有在其他平台上测试过。如果你对Linux开发佷熟悉,相信你

会根据错误提示逐步找到原因并解决,它们一般是你选用的平台缺少了某些库文件或者工具

等 ; 否 则 , 我 们 建 议 初 学 者 使 用 和 我 们 一 致 的 平 台 , 即 Fedora 9( 全 称 为 :

Fedora-9-i386-DVD.iso) , 你 可 以 在 此 下 载

(ftp://download.fedora.redhat.com/pub/fedora/linux/releases/9/Fedora/i386/iso/Fed

ora-9-i386-DVD.iso ),也可以在其他地方获取,它们都是一样的,注意,安装时请务必参考

我们手册提供的步骤,这是我们经过严格测试的,以免遗漏一些开发时所需要的组件。

我们为什么选择 Fedora 9?

根据我们的测试,Fedora 9 经过比较简单的安装和设置,依然可以使用 root 用户登录(大

Administrator
铅笔
Administrator
铅笔

多数开发均基于此用户权限),Fedora 10 及其以后的版本则需要经过稍微复杂的设置才能使用

root,这不利于不了解 Linux 的初学者,Fedora 8 及其以前的版本则相对老了一些。并且按照

我们手册提供的步骤安装 Fedora 9,可以比较完美配合我们提供的开发软件包,不再需要其他

补丁之类的繁琐设置(ubuntu 就需要经常这样更新设置),因此我们认为 Fedora 9 是 适合初学

者的开发平台。

2.3 关于交叉编译器

2.3.1 在何处获取

我们使用的交叉编译器 arm-linux-gcc 4.3.2 主要来自以下网址: http://www.codesourcery.com/sgpp/lite/arm/portal/release644

如下图:

在这里可以看到它的版本是 2008q3,也就是 2008 年第三季,在这里,它是一个免费的

版本,该公司称它为 Lite Edition, 主要是基于命令行的,这对于我们的移植开发都已经够用了。

在写本手册时,已经有更新的版本可用:2009q3,下载地址是: http://www.codesourcery.com/sgpp/lite/arm/portal/release1033

但我们的手册中使用的是 2008q3,它们的差别不是很大。 需要说明的是,codesourcery 公司提供的免费编译器是 核心 基本的功能,有时我们

移植其他开源软件,还需要其他的依赖库,比如 png,zlib, jpeg 等,其实这些也是比较常用的

库,因此我们也已经把它们移植好放在其中,这就形成了我们光盘中的编译器版本。 另外, 该编译器主要是用来交叉编译生成 armv5 体系结构的指令集,我们使用的

s3c2440 属于 ARM920T 系列,它是基于 armv4 体系结构的,需要使用支持 armv4 指令集的编

译器。为了使用方便,我们特意为编译器(主要是 gcc 和 g++)指定了体系结构参数,并重新命

名为常见的 arm-linux-xxx 形式的可执行脚本,如下为 arm-linux-gcc 的脚本内容: #!/bin/bash exec arm-none-linux-gnueabi-gcc -march=armv4t $*

2.3.2 关于 ABI 和 EABI

codesourcery 公司提供的编译器是符合 EABI 标准的编译器,我们公司的文件系统内容

就是基于该编译器制作生成的,因此使用老式的 ABI 接口编译器如 arm-linux-gcc 2.95.3 等编

译出的可执行文件,是不能在上面运行的。 关于 ABI 和 EABI 的描述,见下面的说明: 原文链接:http://linux.chinaunix.net/bbs/thread-1143604-1-8.html 1. 什么是 ABI ABI,application binary interface (ABI),应用程序二进制接口。 既然是 接口,那就是某两种东西之间的沟通桥梁,此处有这些种情况: A。应用程序 <-> 操作系统; B。应用程序 <-> (应用程序所用到的)库 C 。应用程序各个组件之间 类似于 API 的作用是使得程序的代码间的兼容,ABI 目的是使得程序的二进制(级别)

的兼容。 2. 什么是 OABI 和 EABI OABI 中的 O,表示“Old”,“Lagacy”,旧的,过时的,OABI 就是旧的/老的 ABI。 EABI 中的 E,表示“Embedded”,是一种新的 ABI。 EABI 有时候也叫做 GNU EABI。 OABI 和 EABI 都是专门针对 ARM 的 CPU 来说的。 3. EABI 的好处/为何要用 EABI A. 支持软件浮点和硬件实现浮点功能混用 B. 系统调用的效率更高 C. 后今后的工具更兼容

Administrator
铅笔
Administrator
铅笔
Administrator
铅笔
Administrator
铅笔

D. 软件浮点的情况下,EABI 的软件浮点的效率要比 OABI 高很多。 4. OABI 和 EABI 的区别 两种 ABI 在如下方面有区别: A.调用规则(包括参数如何传递及如何获得返回值) B.系统调用的数目以及应用程序应该如何去做系统调用 C.目标文件的二进制格式,程序库等 D.结构体中的 填充(padding/packing)和对齐。

第三章 Linux-2.6.32.2 内核移植详细步骤

3.1 引子

虽然自从 Linux-2.6.31 开始,Linux 内核就已经官方支持 mini2440,但国外爱好者制作

的东西不一定适合咱中国人(特别是国内),况且官方内核对 mini2440 的支持也比较有限;另一

方面,很多初学者对于嵌入式 Linux 移植也一直是兴致浓厚,往往对我们时不时扔出的新内核

知其然不知其所以然,很是“愤慨”,为了满足大家的好奇心,和追根求源的强烈求知欲,我

们在此详细的介绍了如何移植 ARM Linux-2.6.32.2 到 mini2440 的步骤,以飨各位网友和用户 Mini2440 的核心电路和 SMDK2440 基本是一样的,而 Linux-2.6.32.2 内核对 SMDK2440

的支持已经十分丰富,所以大部分关键的移植基本毫不费力,我们只需根据目标平台的细微差

别稍作调整即可。Linux 的发展日新月异,目前 Linux 内核基本每 3 个月就会更新一个较大的

版本,有很多网友可能基于“当时”很潮的内核版本做了移植,并发布过一些移植的日志或者

经验总结,大家也可以参考一下,但要注意取舍,总之要以实际情况为准则。

下面是移植的详细说明。

3.2 获取 Linux 内核源代码

有很多方式可以获取 Linux 内核源代码,如果你的 Fedora9 平台可以上互联网,可以直

接在命令行输入以下命令获取到 原汁原味的 Linux-2.6.32.2: #wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.2.tar.gz 这是我 常用的方式,当然你也可以先在 Windows 系统下使用迅雷等工具下载完,再

复制到 Fedora9 中。

Administrator
铅笔

3.3 解压内核源代码

假定我们刚才把内核源代码下载到了/opt/FriendlyARM/mini2440 目录,执行以下解压命

令: #cd /opt/FriendlyARM/mini2440 #tar xvzf linux-2.6.32.2.tar.gz 这样我们就得到了原始的 Linux-2.6.32.2 源代码,如图

3.4 指定交叉编译变量

我们移植目的是让 Linux-2.6.32.2 可以在 mini2440 上运行。

首先,我们要使得 Linux-2.6.32.2 的缺省目标平台成为 ARM 的平台。

修改总目录下的 Makefile

export KBUILD_BUILDHOST := $(SUBARCH)

ARCH ?= $(SUBARCH)

CROSS_COMPILE ?=

改为

export KBUILD_BUILDHOST := $(SUBARCH)

ARCH ?= arm

CROSS_COMPILE ?= arm-linux-

其中,ARCH 是指定目标平台为 arm,CROSS_COMPILE 是指定交叉编译器,这里指定的

是系统默认的交叉编译器,如要使用其它的,则要把编译器的全路径在这里写出。

提示:我们建议用户使用 vim 编辑器,因为它带有特殊字符颜色显示功能,如图

接下来,要测试一下 linux 的编译是否能正常通过。

执行:

#make s3c2410_defconfig ;使用缺省内核配置文件,s3c2410_defconfig 是 SMDK2440 的

缺省配置文件

#make ;编译时间较长

编译通过,在此我们先不必烧写到开发板验证它的正确性。

3.5 克隆建立自己的目标平台

3.5.1 关于机器码

以上编译是用的 Linux 内核本身支持的目标平台配置,它对应于 SMDK2440。现在我

们要参考 SMDK2440 加入自已的开发板平台,我们使用的是 mini2440,因此取名为 MINI2440。 需要说明的是,Linux-2.6.32.2 本身已经包含了 mini2440 的支持,这样就出现了重名。

那怎么办呢?在此我们依然使用 MINI2440 这个名称,只不过在后面的移植步骤中,把原始内

核自带的 mini2440 代码部分直接删除就可以了,以免和我们自己移植的混淆了。 首先,很关键的一点,内核在启动时,是通过 bootloader 传入的机器码(MACH_TYPE)

确定应启动哪种目标平台的,友善之臂已经为 mini2440 申请了自己的机器码为 1999,它位于

linux-2.6.32.2/arch/arm/tools/mach_types 文件中,如图:

Administrator
铅笔
Administrator
铅笔

如果内核的机器码和 bootloader 传入的不匹配,就会经常出现下面的错误:

Uncompressing Linux.................................................................................................................................. done, booting

the kernel.

运行到这不就停住了。

另外,目前U-boot的官方网站(http://www.denx.de/wiki/U-Boot/WebHome )从

2009.06 版本开始,也已经加入了mini2440 的机器码定义,因此很多其他网友移植的u-boot

都可以直接启动我们将要移植的内核。

提示:在U-boot/include/asm-arm/mach-types.h中可以看到mini2440的机器码定义,如图

接 下 来 , 我 们 注 意 到 linux-2.6.32.2/arch/arm/mach-s3c2440 目 录 下 有 个

mach-mini2440.c 文件,它其实就是国外爱好者为 mini2440 移植添加的主要内容了,但我们不

用它,把它直接删除。

将 linux-2.6.32.2/arch/arm/mach-s3c2440/目录下的 mach-smdk2440.c 复制一份。命名为

mach-mini2440.c , 找 到 MACHINE_START(S3C2440, "SMDK2440") , 修 改 为

MACHINE_START(MINI2440, "FriendlyARM Mini2440 development board")。

提示:开发板运行后,在命令行终端输入:cat /proc/cpuinfo 可以看到我们添加的开发板信息,如图

Administrator
在文本上注释
#cp mach-smdk2440.c mach-mini2440.c #mv mach-smdk2440.c mach-mini2440.c

3.5.2 修改时钟源频率

现在再来修改系统时钟源,在 mach-mini2440.c( 就是我们刚刚通过复制

mach-smdk2440.c 得到的)的第 160 行 static void __init smdk2440_map_io(void)函数中,

把其中的 16934400(代表原 SMDK2440 目标板上的晶振是 16.9344MHz)改为 mini2440 开发板上

实际使用的 12000000(代表 mini2440 开发板上的晶振 12MHz,元器件标号为 X2),如图

3.5.3 从 SMDK2440 到 MINI2440

因为我们要制作自己的 mini2440 平台体系,因此把 mach-mini2440.c 中所有的

smdk2440 字样改为 mini2440,可以使用批处理命令修改,在 vim 的命令模式下输入:

%s/smdk2440/mini2440/g

上面这句的意思是:把所有和“smdk2440”匹配的字符串全部替换为“mini2440”,前

面的“%s“代表字符串匹配, 后的“g”代表 global,是全局的意思,输入如图

Administrator
铅笔
Administrator
铅笔
Administrator
铅笔

除此之外,还有一个地方需要改动,在 mini2440_machine_init(void)函数中,把

smdk_machine_init()函数调用注释掉,因为我们后面会编写自己的初始化函数,不需要调用

smdk2440 原来的,如上图所示。

3.5.4 编译测试

在 Linux 源代码根目录下执行

#make mini2440_defconfig ;使用 Linux 官方自带的 mini2440 配置

#make zImage ;编译内核,时间较长, 后会生成 zImage

重新编译并把生成的内核文件 zImage(位于 arch/arm/boot 目录)下到板子中,可以看

到内核已经可以正常启动了,如下图,但此时大部分硬件驱动还没加,并且也没有文件系统,

因此还无法登陆。

##### FriendlyARM BIOS 2.0 for 2440 #####

[x] format NAND FLASH for Linux

[v] Download vivi

[k] Download linux kernel

[y] Download root_yaffs image

[a] Absolute User Application

[n] Download Nboot for WinCE

[l] Download WinCE boot-logo

[w] Download WinCE NK.bin

[d] Download & Run

[z] Download zImage into RAM

[g] Boot linux from RAM

[f] Format the nand flash

[b] Boot the system

[s] Set the boot parameters

[u] Backup NAND Flash to HOST through USB(upload)

[r] Restore NAND Flash from HOST through USB

[q] Goto shell of vivi

[i] Version: 0945-2K

Enter your selection: b

Copy linux kernel from 0x00060000 to 0x30008000, size = 0x00500000 ... done

zImage magic = 0x016f2818

Setup linux parameters at 0x30000100

linux command line is: "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"

MACH_TYPE = 1999

NOW, Booting Linux......

Uncompressing

Linux.....................................................................................................

............................ done, booting the kernel.

Linux version 2.6.32.2 (root@tom) (gcc version 4.3.2 (Sourcery G++ Lite 2008q3-72) ) #3 Sun Mar 28 17:10:56

CST 2010

CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177

CPU: VIVT data cache, VIVT instruction cache

Machine: FriendlyARM Mini2440 development board

ATAG_INITRD is deprecated; please update your bootloader.

Memory policy: ECC disabled, Data cache writeback

CPU S3C2440A (id 0x32440001)

S3C24XX Clocks, (c) 2004 Simtec Electronics

S3C244X: core 405.000 MHz, memory 101.250 MHz, peripheral 50.625 MHz

CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on

Built 1 zonelists in Zone order, mobility grouping on. Total pages: 16256

Kernel command line: noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

PID hash table entries: 256 (order: -2, 1024 bytes)

Dentry cache hash table entries: 8192 (order: 3, 32768 bytes)

Inode-cache hash table entries: 4096 (order: 2, 16384 bytes)

Memory: 64MB = 64MB total

Memory: 60596KB available (3588K code, 417K data, 132K init, 0K highmem)

SLUB: Genslabs=11, HWalign=32, Order=0-3, MinObjects=0, CPUs=1, Nodes=1

使用 USB 把编译得到的内核烧

写到开发板,然后运行“b“命

令启动,可以看到如下绿色的启

动信息,这说明内核已经启动成

功,但此时大部分硬件驱动还没

Hierarchical RCU implementation.

NR_IRQS:85

irq: clearing subpending status 00000003

irq: clearing subpending status 00000002

Console: colour dummy device 80x30

console [ttySAC0] enabled

Calibrating delay loop... 201.93 BogoMIPS (lpj=504832)

Mount-cache hash table entries: 512

CPU: Testing write buffer coherency: ok

NET: Registered protocol family 16

S3C2440: Initialising architecture

S3C2440: IRQ Support

S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics

DMA channel 0 at c4808000, irq 33

DMA channel 1 at c4808040, irq 34

DMA channel 2 at c4808080, irq 35

DMA channel 3 at c48080c0, irq 36

S3C244X: Clock Support, DVS off

bio: create slab <bio-0> at 0

usbcore: registered new interface driver usbfs

usbcore: registered new interface driver hub

usbcore: registered new device driver usb

s3c-i2c s3c2440-i2c: slave address 0x10

s3c-i2c s3c2440-i2c: bus frequency set to 98 KHz

s3c-i2c s3c2440-i2c: i2c-0: S3C I2C adapter

NET: Registered protocol family 2

IP route cache hash table entries: 1024 (order: 0, 4096 bytes)

TCP established hash table entries: 2048 (order: 2, 16384 bytes)

TCP bind hash table entries: 2048 (order: 1, 8192 bytes)

TCP: Hash tables configured (established 2048 bind 2048)

TCP reno registered

NET: Registered protocol family 1

RPC: Registered udp transport module.

RPC: Registered tcp transport module.

RPC: Registered tcp NFSv4.1 backchannel transport module.

JFFS2 version 2.2. (NAND) © 2001-2006 Red Hat, Inc.

ROMFS MTD (C) 2007 Red Hat, Inc.

msgmni has been set to 118

alg: No test for stdrng (krng)

io scheduler noop registered

io scheduler anticipatory registered (default)

io scheduler deadline registered

io scheduler cfq registered

Console: switching to colour frame buffer device 60x53

fb0: s3c2410fb frame buffer device

s3c2440-uart.0: s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2440

s3c2440-uart.1: s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2440

s3c2440-uart.2: s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2440

brd: module loaded

S3C24XX NAND Driver, (c) 2004 Simtec Electronics

dm9000 Ethernet Driver, V1.31

ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver

s3c2410-ohci s3c2410-ohci: S3C24XX OHCI

s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1

s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000

usb usb1: configuration #1 chosen from 1 choice

hub 1-0:1.0: USB hub found

hub 1-0:1.0: 2 ports detected

usbcore: registered new interface driver libusual

mice: PS/2 mouse device common for all mice

S3C24XX RTC, (c) 2004,2006 Simtec Electronics

i2c /dev entries driver

S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics

s3c2410-wdt s3c2410-wdt: watchdog inactive, reset disabled, irq enabled

cpuidle: using governor ladder

sdhci: Secure Digital Host Controller Interface driver

sdhci: Copyright(c) Pierre Ossman

usbcore: registered new interface driver hiddev

usbcore: registered new interface driver usbhid

usbhid: v2.6:USB HID core driver

Advanced Linux Sound Architecture Driver Version 1.0.21.

No device for DAI UDA134X

No device for DAI s3c24xx-i2s

ALSA device list:

No soundcards found.

TCP cubic registered

NET: Registered protocol family 17

drivers/rtc/hctosys.c: unable to open rtc device (rtc0)

Root-NFS: No NFS server available, giving up.

VFS: Unable to mount root fs via NFS, trying floppy.

VFS: Cannot open root device "mtdblock3" or unknown-block(2,0)

Please append a correct "root=" boot option; here are the available partitions:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(2,0)

[<c002e9f8>] (unwind_backtrace+0x0/0xdc) from [<c02d0390>] (panic+0x40/0x120)

[<c02d0390>] (panic+0x40/0x120) from [<c0008e84>] (mount_block_root+0x1d0/0x210)

[<c0008e84>] (mount_block_root+0x1d0/0x210) from [<c000911c>] (prepare_namespace+0x164/0x1bc)

[<c000911c>] (prepare_namespace+0x164/0x1bc) from [<c000843c>] (kernel_init+0xd8/0x10c)

3.6 关于内核配置菜单中的 mini2440 选项

在开始移植其他驱动之前,我们再了解一些看起来比较“神秘”的常识,那就是运行

make menuconfig 时,内核配置菜单中的 mini2440 选项是如何出现的。 在命令行执行: #make menuconfig ;前面已经执行了 make mini2440_defconfig 加载了缺省配置,因此

这里可以直接执行该命令 出现如图内核配置根菜单

按上下键移动到 System Type,按回车进入该子菜单,如图

再找到 S3C2440 Machines,按回车进入该子菜单,如图

在此就可以看到 Linux 天生内核对 mini2440 开发板的支持选项了,那么它们是从哪里

来的呢? 打开 Linux-2.6.32.2/arch/arm/mach-s3c2440/Kconfig 文件可以找到如图信息

现在明白了吧,“MINI2440 development board”正是在这个 Kconfig 文件中定义说明的,

当然你可以根据自己的喜好改为其他显示信息。 这里的显示信息只是在内核配置菜单中出现的,要让选择的配置实际起效,还需要根

据此配置在 Makefile 中添加相应的代码文件,请看该目录下的 Makefile,如图

这样,配置文件就跟实际的代码文件通过配置定义联系在一起了,这里的配置定义是

“CONFIG_MACH_MINI2440”,内核中还有很多类似的配置定义,并且有的配置定义还存在

依赖关系,我们在此就不对它们详细说明了,随着对内核代码结构的不断熟悉,你就会逐渐学

会分析和查找你所需要的各种配置和定义等。

3.7 移植 Nand 驱动并更改分区信息

3.7.1 Linux-2.6.32.2 内核所支持的 Nand Flash 类型

Linux2.6.32.2 已 经 自 带 了 大 部 分 Nand Flash 驱 动 , 在

linux-2.6.32.2/drivers/mtd/nand/nand_ids.c 文件中,定义了所支持的各种 Nand Flash 类

型,如下图

3.7.2 修改 Nand Flash 分区表

但是系统默认的分区不是我们所需的,所以要自已修改,除此之外,还有 Nand Flash

的结构信息需要增加填写,以便能够适合系统自带的 Nand Flash 驱动接口,这可以参考

SMDK2440 中关于 Nand Flash 设备注册的一些信息。

打开/arch/arm/plat-24xx/common-smdk.c,可以看到这样一个结构体:

static struct mtd_partition smdk_default_nand_part[] = {

[0] = {

.name = "Boot Agent",

.size = SZ_16K,

.offset = 0,

},

[1] = {

.name = "S3C2410 flash partition 1",

.offset = 0,

.size = SZ_2M,

},

[2] = {

.name = "S3C2410 flash partition 2",

.offset = SZ_4M,

.size = SZ_4M,

},

[3] = {

.name = "S3C2410 flash partition 3",

.offset = SZ_8M,

.size = SZ_2M,

},

[4] = {

.name = "S3C2410 flash partition 4",

.offset = SZ_1M * 10,

.size = SZ_4M,

},

[5] = {

.name = "S3C2410 flash partition 5",

.offset = SZ_1M * 14,

.size = SZ_1M * 10,

},

[6] = {

.name = "S3C2410 flash partition 6",

Administrator
铅笔

.offset = SZ_1M * 24,

.size = SZ_1M * 24,

},

[7] = {

.name = "S3C2410 flash partition 7",

.offset = SZ_1M * 48,

.size = SZ_16M,

}

};

这其实就是 Nand Flash 的分区表,在 Linux-2.6.32.2 中,nand 驱动是被注册为平台

设备的,这同样可在/arch/arm/plat-24xx/common-smdk.c 文件中看出,如下: static struct s3c2410_platform_nand smdk_nand_info = {

.tacls = 20,

.twrph0 = 60,

.twrph1 = 20,

.nr_sets = ARRAY_SIZE(smdk_nand_sets),

.sets = smdk_nand_sets,

};

/* devices we initialise */

static struct platform_device __initdata *smdk_devs[] = {

&s3c_device_nand,

&smdk_led4,

&smdk_led5,

&smdk_led6,

&smdk_led7,

};

参考以上结构信息,我们也在自己的 mach-mini2440.c 中照此添加实现,同时需要参

考友善之臂原厂内核中的 Nand 分区表,如下图所示:

因此,在 mach-mini2440.c 中加入以下代码:

;以下蓝色部分为说明文字

static struct mtd_partition mini2440_default_nand_part[] = {

[0] = {

.name = "supervivi", ;这里是 bootloader 所在的分区,可以放置 u-boot, supervivi 等内容,对应

/dev/mtdblock0

.size = 0x00040000,

.offset = 0,

},

[1] = {

.name = "param", ;这里是 supervivi 的参数区,其实也属于 bootloader 的一部分,如果 u-boot 比较

大,可以把此区域覆盖掉,不会影响系统启动,对应/dev/mtdblock1

.offset = 0x00040000,

.size = 0x00020000,

},

Administrator
铅笔

[2] = {

.name = "Kernel", ;内核所在的分区,大小为 5M,足够放下大部分自己定制的巨型内核了,比如内核

使用了更大的 Linux Logo 图片等,对应/dev/mtdblock2

.offset = 0x00060000,

.size = 0x00500000,

},

[3] = {

.name = "root", ;文件系统分区,友善之臂主要用来存放 yaffs2 文件系统内容,对应/dev/mtdblock3

.offset = 0x00560000,

.size = 1024 * 1024 * 1024, //

},

[4] = {

.name = "nand", ;此区域代表了整片的 nand flash,主要是预留使用,比如以后可以通过应用程序访

问读取/dev/mtdblock4 就能实现备份整片 nand flash 了。

.offset = 0x00000000,

.size = 1024 * 1024 * 1024, //

}

};

;这里是开发板的 nand flash 设置表,因为板子上只有一片,因此也就只有一个表

static struct s3c2410_nand_set mini2440_nand_sets[] = {

[0] = {

.name = "NAND",

.nr_chips = 1,

.nr_partitions = ARRAY_SIZE(mini2440_default_nand_part),

.partitions = mini2440_default_nand_part,

},

};

;这里是 nand flash 本身的一些特性,一般需要对照 datasheet 填写,大部分情况下按照以下参数填写即可

static struct s3c2410_platform_nand mini2440_nand_info = {

.tacls = 20,

.twrph0 = 60,

.twrph1 = 20,

.nr_sets = ARRAY_SIZE(mini2440_nand_sets),

.sets = mini2440_nand_sets,

.ignore_unset_ecc = 1,

};

除此之外,还需要把 nand flash 设备注册到系统中,

static struct platform_device *mini2440_devices[] __initdata = {

&s3c_device_usb,

&s3c_device_lcd,

&s3c_device_wdt,

&s3c_device_i2c0,

&s3c_device_iis,

&s3c_device_nand, ;把 nand flash 设备添加到开发板的设备列表结构

};

3.7.3 从启动信息中查看分区表

至此,就完成了 nand flash 驱动的移植,此时在内核根目录执行“make zImage”,把

生成的zImage烧写到开发板,可以在启动时看到如图红色信息,它们正是我们刚刚添加的nand

flash分区信息,以及开发板本身nand flash的一些信息,这里可以看到是256M的nand flash。

S3C24XX NAND Driver, (c) 2004 Simtec Electronics

s3c24xx-nand s3c2440-nand: Tacls=3, 29ns Twrph0=7 69ns, Twrph1=3 29ns

s3c24xx-nand s3c2440-nand: NAND soft ECC

NAND device: Manufacturer ID: 0xec, Chip ID: 0xda (Samsung NAND 256MiB 3,3V 8-bit)

Scanning device for bad blocks

Bad eraseblock 329 at 0x000002920000

Bad eraseblock 399 at 0x0000031e0000

Bad eraseblock 878 at 0x000006dc0000

Bad eraseblock 982 at 0x000007ac0000

Bad eraseblock 1591 at 0x00000c6e0000

Creating 5 MTD partitions on "NAND 256MiB 3,3V 8-bit":

0x000000000000-0x000000040000 : "supervivi"

0x000000040000-0x000000060000 : "param"

ftl_cs: FTL header not found.

0x000000060000-0x000000560000 : "Kernel"

uncorrectable error :

0x000000560000-0x000040560000 : "root"

mtd: partition "root" extends beyond the end of device "NAND 256MiB 3,3V 8-bit" -- size truncated to 0xfaa0000

ftl_cs: FTL header not found.

0x000000000000-0x000040000000 : "nand"

mtd: partition "nand" extends beyond the end of device "NAND 256MiB 3,3V 8-bit" -- size truncated to 0x10000000

dm9000 Ethernet Driver, V1.31

ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver

s3c2410-ohci s3c2410-ohci: S3C24XX OHCI

s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1

s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000

usb usb1: configuration #1 chosen from 1 choice

hub 1-0:1.0: USB hub found

hub 1-0:1.0: 2 ports detected

3.8 移植 yaffs2

3.8.1 获取 yaffs2 源代码

这里是 yaffs 的官方网站主页

现在大部分开发板都可以支持 yaffs2 文件系统,它是专门针对嵌入式设备,特别是使

用 nand flash 作为存储器的嵌入式设备而创建的一种文件系统,早先的 yaffs 仅支持小页

(512byte/page)的 nand flash,现在的开发板大都配备了更大容量的 nand flash,它们一般是大页

模式的(2K/page),使用 yaffs2 就可以支持大页的 nand flash,下面是 yaffs2 的移植详细步骤。 在 http://www.yaffs.net/node/346 可以下载到 新的yaffs2 源代码,需要使用git工具( 安

装方法见本手册第一章),在命令行输入: #git clone git://www.aleph1.co.uk/yaffs2 稍等片刻,就可以下载到 新的 yaffs2 的源代码目录,本光盘中也有单独的 yaffs2 源

代码包( 文件名为:yaffs2-src-20100329.tar.gz)

3.8.2 为内核打上 yaffs2 补丁

然后进入 yaffs2 源代码目录执行: #cd yaffs2 #./patch-ker.sh c /opt/FriendlyARM/mini2440/linux-2.6.32.2 如图,yaffs2 补丁已经成功打上。

Administrator
铅笔
Administrator
铅笔
Administrator
铅笔
Administrator
铅笔

此时进入 linux-2.6.32.2/fs 目录,可以看到已经多了一个 yaffs2 目录。

3.8.3 配置和编译带 YAFFS2 支持的内核

在 Linux 内核源代码根目录运行:make menuconfig,移动上下按键找到 File Systems,如图,按回车进入该子菜单

再找到“Miscellaneous filesystems”菜单项,按回车进入该子菜单,如图

出现如下图菜单,找到“YAFFS2 file system support”,并按空格选中它,这样我们就

在内核中添加了 yaffs2 文件系统的支持,按“Exit”退出内核配置。

在命令行执行: #make zImage

3.8.4 烧写到开发板运行测试

后会生成 linux-2.6.32.2/arch/arm/boot/zImage,使用 supervivi 的“k“功能把它烧写

到 nand flash,按“b“启动系统,这时,如果 nand flash 已经存在文件系统(可以使用 supervivi的“y“功能烧写友善之臂提供的现成的 yaffs2 文件系统映像 root_qtopia-128M.img 用以测试),就会看到如图信息了,这说明 yaffs2 已经移植成功。

3.9 移植 DM9000 网卡驱动

3.9.1 设备资源初始化

Linux-2..6.32.2 已经自带了完善的 DM9000 网卡驱动驱动(源代码位置:linux-2.6.32.2/ drivers/net/dm9000.c),它也是一个平台设备,因此在目标平台初始化代码中,只要填写好相

应的结构表即可,具体步骤如下: 首先添加驱动所需的头文件 dm9000.h:

#include <linux/dm9000.h>

Administrator
铅笔

再定义 DM9000 网卡设备的物理基地址,以便后面用到: /* DM9000AEP 10/100 ethernet controller */ #define MACH_MINI2440_DM9K_BASE (S3C2410_CS4 + 0x300)

再填充该平台设备的资源设置,以便和 DM9000 网卡驱动接口配合起来,如下 static struct resource mini2440_dm9k_resource[] = { [0] = { .start = MACH_MINI2440_DM9K_BASE, .end = MACH_MINI2440_DM9K_BASE + 3, .flags = IORESOURCE_MEM }, [1] = { .start = MACH_MINI2440_DM9K_BASE + 4, .end = MACH_MINI2440_DM9K_BASE + 7, .flags = IORESOURCE_MEM }, [2] = { .start = IRQ_EINT7, .end = IRQ_EINT7, .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE, } }; /* * * * The DM9000 has no eeprom, and it's MAC address is set by * * * the bootloader before starting the kernel. * * */ static struct dm9000_plat_data mini2440_dm9k_pdata = { .flags = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM), }; static struct platform_device mini2440_device_eth = { .name = "dm9000", .id = -1, .num_resources = ARRAY_SIZE(mini2440_dm9k_resource), .resource = mini2440_dm9k_resource, .dev = { .platform_data = &mini2440_dm9k_pdata, }, }; ;同时在 mini2440 设备集中添加上面做好的网卡平台设备,如下红色部分

static struct platform_device *mini2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &mini2440_device_eth, &s3c_device_nand, };

这样,DM9000 平台设备的接口就填完了。

3.9.2 调整 DM9000 所用的位宽寄存器

因为 Linux-2.6.32.2 的 DM9000 网卡驱动并不是专门为 mini2440 准备的,所以还要在

其源代码中做一些移植工作,如下步骤。 打开 linux-2.6.32.2/ drivers/net/dm9000.c,头文件处添加 2410 相关的配置定义,如下

红色部分: #include <asm/delay.h> #include <asm/irq.h> #include <asm/io.h> #if defined(CONFIG_ARCH_S3C2410) #include <mach/regs-mem.h> #endif #include "dm9000.h"

在 dm9000 设备的初始化函数中添加如下红色部分,这里是配置 DM9000 所用片选总

线的时序,因为 mini2440 目前只有一个通过总线外扩的设备,在此设备驱动中直接修改相关

的寄存器配置会更加容易理解一些,当然这部分也可以放到 mach-mini2440.c 中,你可以自行

实验一下,在此不再赘述。 static int __init dm9000_init(void) { #if defined(CONFIG_ARCH_S3C2410) unsigned int oldval_bwscon = *(volatile unsigned int *)S3C2410_BWSCON; unsigned int oldval_bankcon4 = *(volatile unsigned int *)S3C2410_BANKCON4; *((volatile unsigned int *)S3C2410_BWSCON) = (oldval_bwscon & ~(3<<16)) | S3C2410_BWSCON_DW4_16 | S3C2410_BWSCON_WS4 | S3C2410_BWSCON_ST4;

Administrator
铅笔

*((volatile unsigned int *)S3C2410_BANKCON4) = 0x1f7c; #endif printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME, DRV_VERSION); return platform_driver_register(&dm9000_driver); }

3.9.3 关于 MAC 地址

需要注意的是,本开发板所用的 DM9000 网卡并没有外接 EEPROM 用以存储 MAC 地

址,因此系统中的 MAC 地址是一个“软”地址,也就是可以通过软件进行修改,可以随意改

为其他值,在 static int __devinit dm9000_probe(struct platform_device *pdev)函数中可以看出: /* try reading the node address from the attached EEPROM */ ;尝试从 EEPROM 读取 MAC 地址 for (i = 0; i < 6; i += 2) dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i); if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) { mac_src = "platform data"; memcpy(ndev->dev_addr, pdata->dev_addr, 6); } if (!is_valid_ether_addr(ndev->dev_addr)) { /* try reading from mac */ mac_src = "chip"; for (i = 0; i < 6; i++) ndev->dev_addr[i] = ior(db, i+DM9000_PAR); } ;使用“软”MAC 地址: 08:90:90:90:90:90 memcpy(ndev->dev_addr, "\x08\x90\x90\x90\x90\x90", 6); if (!is_valid_ether_addr(ndev->dev_addr)) dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please " "set using ifconfig\n", ndev->name);

实际上到此为止 DM9000 就已经移植结束了。

Administrator
铅笔

3.9.4 配置内核加入 DM9000,并编译运行测试

此时会带内核源代码目录,执行: #make menuconfig 开始在内核中配置网卡驱动,依次选择如下菜单项

Device Drivers --->Network device support ---> Ethernet (10 or 100Mbit) ---> 即可找到 DM9000 的配置项,可以看到 DM9000 已经被选中,这是因为 Linux-2.6.32.2

默认的内核配置已经加入了 DM9000 的支持

然后执行: #make zImage 后生成 arch/arm/boot/zImage 文件,使用”k”命令把它烧写到开发板,并使用默认的

文件系统启动,在命令行终端运行 ifconfig 命令可以看到如图所示。

3.10 激活 RTC 驱动

3.10.1 在初始化文件中加入 RTC 设备结构

Linux-2.6.32.2 内核对 2440 的 RTC 驱动已经十分完善了,但并未在 mach-mini2440.c中的设备集中加入它,因此并没有被激活,加入 RTC 结构体如下红色字体: ;在 mini2440 设备集中加入 RTC 结构体 static struct platform_device *mini2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_rtc, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &mini2440_device_eth, &s3c_device_nand, };

Administrator
在文本上注释
RTC(Real-Time Clock):实时时钟。它的主要作用就是提供稳定的时钟信号给后续电路用。
Administrator
铅笔

3.10.2 在内核中配置 RTC

接下来重新配置内核,以加入 RTC 的驱动支持,依次选择如下菜单项: Device Drivers ---> <*> Real Time Clock --->

出现如图菜单

可以看到这里缺省配置已经选择了 RTC 相关的选项,这里特别要注意的是该配置菜单

下方的<*> Samsung S3C series SoC RTC 选项支持,因为这里才是内核中真正的 2440 之

RTC 驱动配置项。

3.10.3 测试 RTC

退出内核配置菜单,执行: #make zImage 把生成的 arch/arm/boot/zImage 烧写到开发板,就可以在/dev 目录下看到/dev/rtc 设备

驱动了。如图

要测试 RTC,可以参考 mini2440 的用户手册 2.5.22 章节(因手册更新,有可能具体章

节会变动,请按照实际章节查询即可),这里摘录如下: Linux 中更改时间的方法一般使用 date 命令,为了把 S3C2440 内部带的时钟与 linux 系统时钟同步,

一般使用 hwclock 命令,下面是它们的使用方法: (1) date -s 042916352007 #设置时间为 2007-04-29 16:34 (2) hwclock -w #把刚刚设置的时间存入 S3C2440 内部的 RTC (3).开机时使用 hwclock -s 命令可以恢复 linux 系统时钟为 RTC, 一般把该语句放入

/etc/init.d/rcS 文件自动执行。 注意:我们提供的系统已经把 hwclock –s 命令写入 rcS 文件。

运行时的截图如下:

Administrator
铅笔
Administrator
铅笔
Administrator
铅笔

3.11 添加 LCD 背光驱动(带详细注解)

3.11.1 LCD 背光控制原理

到目前为止,我们一直都在命令行下移植,查看结果,LCD 屏幕上似乎总是如伸手不

见五指的黑夜,神秘而又无可奈何,从这一小节开始,我们就要打开这道神秘之门了。 在 mini2440/micro2440 开发板中,LCD 背光是通过 CPU 的 LCD_PWR 引脚来控制的,

从原理图中可以看出,它对应于 GPG4,如图

Administrator
铅笔

当 LCD_PWR 输出为高电平“1”时,将打开背光;当输出为低电平“0”时,将关闭

背光(注意:这里只是打开和关闭背光,而并没有背光亮度的调节作用)。

3.11.2 在内核中添加背光驱动程序

现在,我们需要增加一个简单的背光驱动,以便能够通过软件便可简单的控制背光的

开关。我们要达到的目的是:在命令终端通过向背光设备发送偶数比如“0”便可关闭背光,

发送奇数比如“1”便可打开背光,这样使用起来就方便多了,而不需要专门的应用程序控制

它,正如在用户手册中所描述的方法(2.5.10 控制 LCD 的背光): 提示:LCD 背光设备文件:/dev/backlight 在命令行种输入:echo 0 > /dev/backlight 可以关闭 LCD 背光。 在命令行种输入:echo 1 > /dev/backlight 可以打开 LCD 背光。 为了实现这点,我们在 linux-2.6.32.2/drivers/video 目录增加一个 mini2440_backlight.c

文件,内容如下: ;以下头文件可能并不是每一个都必须的,但多余的并不会影响驱动程序的内容 #include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/init.h> #include <linux/serio.h> #include <linux/delay.h> #include <linux/clk.h> #include <linux/miscdevice.h> #include <linux/gpio.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/uaccess.h> #include <mach/regs-clock.h> #include <plat/regs-timer.h> #include <mach/regs-gpio.h> #include <linux/cdev.h> #undef DEBUG //#define DEBUG #ifdef DEBUG

Administrator
铅笔
Administrator
铅笔

#define DPRINTK(x...) {printk(__FUNCTION__"(%d): ",__LINE__);printk(##x);} #else #define DPRINTK(x...) (void)(0) #endif ;定义背光驱动的名称为 backligh,将会出现在/dev/backlight #define DEVICE_NAME "backlight" ;定义背光变量 bl_state,以记录背光的开关状态 static unsigned int bl_state; ;设置背光开关的函数,主要是翻转背光变量 bl_state static inline void set_bl(int state) { bl_state = !!state; //翻转 bl_state 变量 s3c2410_gpio_setpin(S3C2410_GPG(4), bl_state); //把结果写入背光所用的寄存器 GPG4 } ;获取背光状态 static inline unsigned int get_bl(void) { return bl_state; } ;从应用程序读取参数,并传递到内核中 static ssize_t dev_write(struct file *file, const char *buffer, size_t count, loff_t * ppos) { unsigned char ch; int ret; if (count == 0) { return count; } ;使用 copy_from_user 函数从用户层/应用层读取参数 ret = copy_from_user(&ch, buffer, sizeof ch) ? -EFAULT : 0; if (ret) { return ret; } ch &= 0x01; //判断奇数还是偶数

Administrator
铅笔

set_bl(ch); //设置背光状态 return count; } ;把内核参数传递给用户层/应用层的读函数 static ssize_t dev_read(struct file *filp, char *buffer, size_t count, loff_t *ppos) { int ret; unsigned char str[] = {'0', '1' }; if (count == 0) { return 0; } ;使用 copy_to_user 函数把内核参数传递到用户层/应用层 ret = copy_to_user(buffer, str + get_bl(), sizeof(unsigned char) ) ? -EFAULT : 0; if (ret) { return ret; } return sizeof(unsigned char); } ;设备操作集 static struct file_operations dev_fops = { owner: THIS_MODULE, read:dev_read, write: dev_write, }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; ;设备初始化,内核启动时就有效 static int __init dev_init(void)

{ int ret; ret = misc_register(&misc); printk (DEVICE_NAME"\tinitialized\n"); ;初始化背光所用的端口 GPG4 为输出 s3c2410_gpio_cfgpin(S3C2410_GPG(4), S3C2410_GPIO_OUTPUT); ;启动内核时打开背光 set_bl(1); return ret; } static void __exit dev_exit(void) { misc_deregister(&misc); } module_init(dev_init); //注册背光驱动模块 module_exit(dev_exit); //卸载背光驱动模块 MODULE_LICENSE("GPL"); MODULE_AUTHOR("FriendlyARM Inc.");

然后把背光配置选项加入内核配置菜单,打开 linux-2.6.32.2/drivers/video/Kconfig,在

如图位置加入: config FB_S3C2410_DEBUG bool "S3C2410 lcd debug messages" depends on FB_S3C2410 help Turn on debugging messages. Note that you can set/unset at run time through sysfs #在里加入 MINI2440 的背光驱动配置 config BACKLIGHT_MINI2440 tristate "Backlight support for mini2440 from FriendlyARM" depends on MACH_MINI2440 && FB_S3C2410 help backlight driver for MINI2440 from FriendlyARM

config FB_SM501 tristate "Silicon Motion SM501 framebuffer support" depends on FB && MFD_SM501 select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT

再打开 linux-2.6.32.2/drivers/video/Makefile,根据配置定义加入驱动目标文件,如图: # the test framebuffer is last obj-$(CONFIG_FB_VIRTUAL) += vfb.o #video output switch sysfs driver obj-$(CONFIG_VIDEO_OUTPUT_CONTROL) += output.o obj-$(CONFIG_BACKLIGHT_MINI2440) += mini2440_backlight.o

这样,我们就在内核中移植好了 mini2440 的背光驱动,在内核源代码根目录执行:

make menuconfig,依次选择如下子菜单: Device Drivers ---> Graphics support ---> <*> Support for frame buffer devices --->

就可以找到该配置选项,如图

在这里,按空格选中我们刚刚加入的 mini2440 配置项,然后退出保存内核配置菜单,

在命令行执行:make zImage 将生成 arch/arm/boot/zImage,使用 supervivi 的“k”功能把它烧写到开发板中,可以

在启动时看到如图所示的企鹅图像,这说明我们已经点亮了背光,只不过 LCD 驱动还有些问

题,下一节我们将会详细的介绍如何移植 LCD 驱动。

3.12 移植 LCD 显示驱动

3.12.1 LCD 驱动基础知识

Linux-2.6.32.2 内核已经支持 S3C2440 的 LCD 控制器驱动,但在此我们先介绍一下关

于 2440 LCD 控制器以及驱动相关的 LCD 的一些基础知识。 注意:在此我们只讨论 TFT LCD,也就是真彩屏。 LCD 驱动中 关键的就是时钟频率(Clock frequency)的设置,时钟频率设置不对,LCD

的显示就会闪,或者根本没有显示。一般 LCD 的 Datasheet 上会写有一个推荐的频率,比如

mini2440 所用的统宝 3.5”LCD,在它的数据手册第 13 页,有这样一个表格:

可以看到,这里推荐的时钟频率是 6.39MHz,近似于 6.4MHz,范围,是 5M-6.85MHz。

S3C2440之LCD控制器与此相关的设置为CLKVAL,通过设置它,就可以在LCD接口的VCLK引脚上产生 LCD 所需要的时钟频率,那么 CLKVAL 和 VCLK 有何种关系呢?在 2440 手册(411页)中,有这样一段描述: The rate of VCLK signal depends on the CLKVAL field in the LCDCON1 register. Table 15-3 defines the relationship of VCLK and CLKVAL. The minimum value of CLKVAL is 0

接下来,手册中提供了它们的数学关系公式:

Administrator
铅笔

VCLK(Hz) = HCLK/[(CLKVAL+1)x2] 因此可以得出:

VCLK = HCLK / ((CLKVAL+1)*2) 那么 HCLK 是多少呢?我们的开发板运行于 400Mhz,这个可以在 bootloader 的源代

码头文件中看到,如图:

可见,FCLK:HCLK:PCLK = 1:4:8,因此得出 HCLK=100Mhz,再根据上述公式得出

CLKVAL 应为: CLKVAL=HCLK/(VCLK*2) -1 CLKVAL = 100000000 / (6400000 * 2) - 1 = 6.8 选择 接近的整数值 7,并把它写入 LCDCON1:17-8(注意:我们实际使用的数值是 8),

由此产生的 VCLK 频率实测为 5.63Mhz 左右,它也是在 5-6.85Mhz 之间的数值,如图:

3.12.2 新内核中的 pixclock 参数

在以前较老的 Linux 内核中,对于 LCD 寄存器的设置都是这样直接填写 CLKVAL 的,

但 Linux-2.6.32.2 内核却不再使用这样简单直观的方式,而是通过一个称为“pixclock”的参数

进行调节,它的计算变的复杂和难以理解,我们不清楚 Linux 内核中关于 2440 部分的移植为

何改变成这样的方式,这有可能是为了和 X86 体系中的设置保持一致的风格,下面我们根据

实际的代码进行一些推导和说明,但推导结果和我们的实际设置是并不一致的,会有一些误差。 提示:我们实际提供的 pixclock 参数并不是按照以下的方式推导计算出的,而是先确

定好 CLKVAL 的数值,再反复尝试、猜测得到的。 在 Framebuffer 驱动(linux-2.6.32.2/ drivers/video/s3c2410fb.c)中有这样一个函数: clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi, var->pixclock), 2); 这里的 clkdiv 就是我们上面提到的 CLKVAL,而 DIV_ROUND_UP 是一个宏定义,它

位于 include/linux/kernel.h 文件中: #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) 这其实是一个数学概念:向上取整。下面是关于“向上取整”的一段说明:

以下信息来自:http://www.vckbase.com/document/viewdoc/?id=743 1. 问题 A,B 都是整数并且 A>1, B>1 求 ┌ A/B ┐ 即 A/B 的上取整。

当 A/B 整除,往上取整返回值 为 A/B。 当 不整除,返回值是 int(A/B) + 1 这个算法的一个应用:如果你有一个动态增长的缓冲区,增长的步长是 B, 某一次缓冲区申请的大小是 A,这个时候,就可以用这个算法,计算出缓冲区的一个合 适大小了,正好可以容纳 A,并且不会过于得多,多余部分不会比 B 多。 2. 方法 int( (A+B-1)/B ) 3. HUNTON 的证明 上取整用 UP 表示 由于 A>1、B>1,且 A、B 都是整数,所以可以设 A=NB+M 其中 N 为非负整数,M 为 0 到 B-1 的数,则 A/B = N + M/B (A+B-1)/B = N + 1 + (M - 1)/B; 当 M 为 0 时, UP(A/B) = N, int((A+B-1)/B) = N + int(1 - 1/B) = N 当 M 为 1 到 B-1 的数时,0 <= M-1 <= B-2 UP(A/B) = N + 1, int((A+B-1)/B) = N + 1 + int((M-1)/B) = N + 1 所以对 A>1、B>1 的整数 A、B 都有: UP(A/B) = int((A+B-1)/B)

对于除数为“2”的本算法而言,我们可以简单的理解为“(n/2)+0.5”所对应的整数值,

因此这里不可能避免的就出现了误差,也就是说 n 的数值是有一定范围的,这里的 n 就是

“s3c2410fb_calc_pixclk(fbi, var->pixclock)”,因此上面的公式可以改写为: clkdiv= s3c2410fb_calc_pixclk(fbi, var->pixclock)/2 + 0.5 而 s3c2410fb_calc_pixclk(fbi, var->pixclock) 这 个 函 数 在 linux-2.6.32.2/

drivers/video/s3c2410fb.c 中是这样定义的: /* s3c2410fb_calc_pixclk() * * calculate divisor for clk->pixclk */ static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi, unsigned long pixclk) { unsigned long clk = fbi->clk_rate; unsigned long long div; /* pixclk is in picoseconds, our clock is in Hz *

Administrator
铅笔

* Hz -> picoseconds is / 10^-12 */ ;这里计算出本函数的结果 div = (unsigned long long)clk * pixclk; div >>= 12; /* div / 2^12 */ do_div(div, 625 * 625UL * 625); /* div / 5^12 */ dprintk("pixclk %ld, divisor is %ld\n", pixclk, (long)div); return div; }

因此得出: clkdiv=clk*pixclk/(10^12)/2 + 0.5 根据实际打印结果验证,此处的 clk 其实就是 HCLK。 而根据 static void s3c2410fb_activate_var(struct fb_info *info)函数中的描述,会得出

这样一个关系: CLKVAL=clkdiv-1 再结合从 2440 芯片手册得到的公式 CLKVAL=HCLK/(VCLK*2) -1,因此可以得出大

致这样的结果(“大致”可以理解为一定的误差范围): Pixclk=(HCLK-VLCK)x10^12/HCLK*VCLK 以我们所用的统宝屏为例: HCLK=100Mhz=100,000,000Hz VLCK=6.4Mhz=6400,000Hz 因此计算出:pixclk =146250,单位是 ps(picoseconds),这和我们实际设置的数值 170000

是有一定误差的。 另 外 , 在 Linux 内 核 文 档 中 , 还 有 另 外 一 种 计 算 pixclock 的 方 式 , 见

linux/Documentation/fb/framebuffer.txt,在此我们就不再详细介绍了,感兴趣的可以自己看下,

或者到网上查下相关资料。 如果你对这些参数比较“晕”,我们建议你按照我们已经移植验证好的参数进行设置,

下面是具体的参考步骤。

3.12.3 在内核中添加各种 LCD 类型的支持

打开 arch/arm/mach-s3c2440/mach-mini2440.c,先删除之前的 LCD 设备平台代码,如

下: /* LCD driver info */ static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = { .lcdcon5 = S3C2410_LCDCON5_FRM565 |

Administrator
在文本上注释
皮秒,百亿分之一秒

S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_PWREN | S3C2410_LCDCON5_HWSWP, .type = S3C2410_LCDCON1_TFT, .width = 240, .height = 320, .pixclock = 166667, /* HCLK 60 MHz, divisor 10 */ .xres = 240, .yres = 320, .bpp = 16, .left_margin = 20, .right_margin = 8, .hsync_len = 4, .upper_margin = 8, .lower_margin = 7, .vsync_len = 4, }; static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = { .displays = &smdk2440_lcd_cfg, .num_displays = 1, .default_display = 0, #if 0 /* currently setup by downloader */ .gpccon = 0xaa940659, .gpccon_mask = 0xffffffff, .gpcup = 0x0000ffff, .gpcup_mask = 0xffffffff, .gpdcon = 0xaa84aaa0, .gpdcon_mask = 0xffffffff, .gpdup = 0x0000faff, .gpdup_mask = 0xffffffff, #endif .lpcsel = ((0xCE6) & ~7) | 1<<4,

}; 再把友善之臂已经移植好的代码加入,如下:

/* LCD driver info */ ;NEC 3.5”LCD 的配置和参数设置 #if defined(CONFIG_FB_S3C2410_N240320) #define LCD_WIDTH 240 #define LCD_HEIGHT 320 #define LCD_PIXCLOCK 100000 #define LCD_RIGHT_MARGIN 36 #define LCD_LEFT_MARGIN 19 #define LCD_HSYNC_LEN 5 #define LCD_UPPER_MARGIN 1 #define LCD_LOWER_MARGIN 5 #define LCD_VSYNC_LEN 1 ;夏普 8”LCD 的配置和参数设置 #elif defined(CONFIG_FB_S3C2410_TFT640480) #define LCD_WIDTH 640 #define LCD_HEIGHT 480 #define LCD_PIXCLOCK 80000 #define LCD_RIGHT_MARGIN 67 #define LCD_LEFT_MARGIN 40 #define LCD_HSYNC_LEN 31 #define LCD_UPPER_MARGIN 25 #define LCD_LOWER_MARGIN 5 #define LCD_VSYNC_LEN 1 ;统宝 3.5”LCD 的配置和参数设置 #elif defined(CONFIG_FB_S3C2410_T240320) #define LCD_WIDTH 240 #define LCD_HEIGHT 320 #define LCD_PIXCLOCK 146250//170000

#define LCD_RIGHT_MARGIN 25 #define LCD_LEFT_MARGIN 0 #define LCD_HSYNC_LEN 4 #define LCD_UPPER_MARGIN 1 #define LCD_LOWER_MARGIN 4 #define LCD_VSYNC_LEN 1 ;群创 7”LCD 的配置和参数设置 #elif defined(CONFIG_FB_S3C2410_TFT800480) #define LCD_WIDTH 800 #define LCD_HEIGHT 480 #define LCD_PIXCLOCK 11463//40000 #define LCD_RIGHT_MARGIN 67 #define LCD_LEFT_MARGIN 40 #define LCD_HSYNC_LEN 31 #define LCD_UPPER_MARGIN 25 #define LCD_LOWER_MARGIN 5 #define LCD_VSYNC_LEN 1 ;LCD2VGA(分辨率为 1024x768)模块的配置和参数设置 #elif defined(CONFIG_FB_S3C2410_VGA1024768) #define LCD_WIDTH 1024 #define LCD_HEIGHT 768 #define LCD_PIXCLOCK 80000 #define LCD_RIGHT_MARGIN 15 #define LCD_LEFT_MARGIN 199 #define LCD_HSYNC_LEN 15 #define LCD_UPPER_MARGIN 1 #define LCD_LOWER_MARGIN 1 #define LCD_VSYNC_LEN 1 #define LCD_CON5 (S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_HWSWP) #endif

#if defined (LCD_WIDTH) static struct s3c2410fb_display mini2440_lcd_cfg __initdata = { #if !defined (LCD_CON5) .lcdcon5 = S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_PWREN | S3C2410_LCDCON5_HWSWP, #else .lcdcon5 = LCD_CON5, #endif .type = S3C2410_LCDCON1_TFT, .width = LCD_WIDTH, .height = LCD_HEIGHT, .pixclock = LCD_PIXCLOCK, .xres = LCD_WIDTH, .yres = LCD_HEIGHT, .bpp = 16, .left_margin = LCD_LEFT_MARGIN + 1, .right_margin = LCD_RIGHT_MARGIN + 1, .hsync_len = LCD_HSYNC_LEN + 1, .upper_margin = LCD_UPPER_MARGIN + 1, .lower_margin = LCD_LOWER_MARGIN + 1, .vsync_len = LCD_VSYNC_LEN + 1, }; static struct s3c2410fb_mach_info mini2440_fb_info __initdata = { .displays = &mini2440_lcd_cfg, .num_displays = 1, .default_display = 0, .gpccon = 0xaa955699, .gpccon_mask = 0xffc003cc, .gpcup = 0x0000ffff,

.gpcup_mask = 0xffffffff, .gpdcon = 0xaa95aaa1, .gpdcon_mask = 0xffc0fff0, .gpdup = 0x0000faff, .gpdup_mask = 0xffffffff, .lpcsel = 0xf82, }; #endif

然后打开 drivers/video/Kconfig,在大概 1935 行加入以下配置信息: config FB_S3C2410_DEBUG bool "S3C2410 lcd debug messages" depends on FB_S3C2410 help Turn on debugging messages. Note that you can set/unset at run time through sysfs choice prompt "LCD select" depends on FB_S3C2410 help S3C24x0 LCD size select config FB_S3C2410_T240320 boolean "3.5 inch 240X320 Toppoly LCD" depends on FB_S3C2410 help 3.5 inch 240X320 Toppoly LCD config FB_S3C2410_N240320 boolean "3.5 inch 240X320 NEC LCD" depends on FB_S3C2410 help 3.5 inch 240x320 NEC LCD config FB_S3C2410_TFT640480

boolean "8 inch 640X480 L80 LCD" depends on FB_S3C2410 help 8 inch 640X480 LCD config FB_S3C2410_TFT800480 boolean "7 inch 800x480 TFT LCD" depends on FB_S3C2410 help 7 inch 800x480 TFT LCD config FB_S3C2410_VGA1024768 boolean "VGA 1024x768" depends on FB_S3C2410 help VGA 1024x768 endchoice config BACKLIGHT_MINI2440 tristate "Backlight support for mini2440 from FriendlyARM" depends on MACH_MINI2440 && FB_S3C2410 help backlight driver for MINI2440 from FriendlyARM config FB_SM501 tristate "Silicon Motion SM501 framebuffer support" depends on FB && MFD_SM501 select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT

这样,我们就完成了 LCD 驱动的移植,如果你需要加入其他型号的 LCD 驱动,也可

以参照上面的方式复制即可,一般小尺寸的 pixclock 参数可以参考统宝 3.5”的,超过 640x480分辨率的参数可以参考 8”LCD 的,特别要注意你使用的 LCD 的长宽也要修改。

3.12.4 配置内核并下载到开发板测试

现在,我们在命令行输入:make menuconfig 进入内核配置,依次按下面的子菜单项选

择:

Device Drivers ---> Graphics support --->

<*> Support for frame buffer devices ---> LCD select (3.5 inch 240X320 Toppoly LCD) --->

会出现如图所示 LCD 型号配置选项:

按空格或者回车键选择我们需要的 LCD 型号,然后退出保存内核配置。 在命令行执行: #make zImage 将会生成 arch/arm/boot/zImage,把它烧写到开发板中,就可以看到一个小企鹅出现

在屏幕上了,如图

3.13 修改 Linux Logo

3.13.1 使用命令行工具修改 Linux LOGO

在上一小节中我们看到 Linux 系统启动时会出现一个小企鹅图像:

大 部 分 Linux 系 统 会 有 这 个 开 机 图 片 , 它 对 应 的 文 件 其 实 就 是

linux-2.6.32.2/drivers/video/logo/linux_logo_clut224.ppm 该文件是一个特殊格式的图像文件,有很多方法可以把普通的图片转换为 logo 文件,

常用的就是 netpbm 工具组。 “netpbm”是一组命令行的工具,它可以转换很多格式的图片,在此以 png 格式为例

介绍一下如何把普通的 PNG 文件转换为我们需要的 Linux LOGO 图片: 假定我们要转换的文件名为 linux_logo.png,首先将 png 图片转成 pnm # pngtopnm linux_logo.png > linux_logo.pnm 然后将 pnm 图片的颜色数限制在 224 # pnmquant 224 linux_logo.pnm > linux_logo_clut224.pnm 后将 pnm 图片转换成我们需要的 ppm

# pnmtoplainpnm linux_logo_clut224.pnm > linux_logo_clut224.ppm 然 linux_logo_clut224.ppm 替换 linux-2.6.32.2/drivers/video/logo 中对应的图像就可以

了。

3.13.2 使用图形化的 LogoMaker 制作 Linux LOGO

为了让用户使用的更方便些,我们设计了一个图形界面的制作工具 LogoMaker,它基

于 Fedora 9 平台开发,其实它的底层调用的就是上述命令行工具,如果你运行的结果出现浮点

数错误,那可能你使用的平台并非 Federa9,如果在 Fedora9 下运行出现如图错误,你可能没

有正确安装 netpbm 工具(我们强烈推荐用户按照我们手册介绍的步骤安装 Fedora9 平台):

下面是 Linux LogoMaker 的使用步骤(摘自 mini2440 用户手册), 请先按照用户手册第五章节安装好 logomaker工具程序,在任意命令行输入 logomaker,

就可以启动它,打开时它会显示一幅缺省的花朵图片,如图,。

点 File->Open a picture file…或者使用快捷键 Ctrl+O 可以打开一个图片文件,在跳出的

文件打开窗口中选择一个图片:

图片会显示在 logomaker 窗口中,如图

这时点 File->Convert the picture to a Linux Logo File,或者使用快捷键 Crtl+C 会跳出文

件保存目录窗口,不需要输入任何东西,选择要保存的目录即可,文件名将会自动保存为

linux_logo_clut224.ppm,使用这个文件代替 linux-2.6.32.2/drivers/video/logo 目录下的同名文件

即可。

LogoMaker 的简易说明如图所示(点菜单“help->About”可以打开它):

3.14 添加 ADC 驱动

3.14.1 关于 S3C2440 的 ADC 和触摸屏接口

Linux-2.6.32.2 内核并没有提供支持 S3C2440 的 ADC 驱动程序,因此我们自行设计了

一个,这个驱动比较简单,属于字符设备,它位于 drivers/char 目录下,驱动程序的文件名为: mini2440_adc.c。

在 S3C2440 芯片中,AD 输入和触摸屏接口使用共同的 A/D 转换器,见 2440 芯片手

册第 16 章节,如图。

3.14.2 在内核中添加 ADC 驱动

因此,ADC 驱动和触摸屏驱动若想共存,就必须解决共享“A/D 转换器”资源这个问

题,因此在 ADC 驱动程序中声明了一个全局的“ADC_LOCK”信号量,ADC 驱动程序的内

容和注解如下: #include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/init.h>

#include <linux/serio.h> #include <linux/delay.h> #include <linux/clk.h> #include <linux/wait.h> #include <linux/sched.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/uaccess.h> #include <mach/regs-clock.h> #include <plat/regs-timer.h> #include <plat/regs-adc.h> #include <mach/regs-gpio.h> #include <linux/cdev.h> #include <linux/miscdevice.h> ;自己定义的头文件,因原生内核并没有包含 #include "s3c24xx-adc.h" #undef DEBUG //#define DEBUG #ifdef DEBUG #define DPRINTK(x...) {printk(__FUNCTION__"(%d): ",__LINE__);printk(##x);} #else #define DPRINTK(x...) (void)(0) #endif ;定义 ADC 转换设备名称,将出现在/dev/adc #define DEVICE_NAME "adc" static void __iomem *base_addr; ;定义 ADC 设备结构 typedef struct { wait_queue_head_t wait; int channel; int prescale; }ADC_DEV;

;声明全局信号量,以便和触摸屏驱动程序共享 A/D 转换器 DECLARE_MUTEX(ADC_LOCK); ;ADC 驱动是否拥有 A/D 转换器资源的状态变量 static int OwnADC = 0; static ADC_DEV adcdev; static volatile int ev_adc = 0; static int adc_data; static struct clk *adc_clock; ;定义 ADC 相关的寄存器 #define ADCCON (*(volatile unsigned long *)(base_addr + S3C2410_ADCCON)) //ADC control #define ADCTSC (*(volatile unsigned long *)(base_addr + S3C2410_ADCTSC)) //ADC touch screen control #define ADCDLY (*(volatile unsigned long *)(base_addr + S3C2410_ADCDLY)) //ADC start or Interval Delay #define ADCDAT0 (*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT0)) //ADC conversion data 0 #define ADCDAT1 (*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT1)) //ADC conversion data 1 #define ADCUPDN (*(volatile unsigned long *)(base_addr + 0x14)) //Stylus Up/Down interrupt status #define PRESCALE_DIS (0 << 14) #define PRESCALE_EN (1 << 14) #define PRSCVL(x) ((x) << 6) #define ADC_INPUT(x) ((x) << 3) #define ADC_START (1 << 0) #define ADC_ENDCVT (1 << 15) ;定义“开启 AD 输入”宏,因为比较简单,故没有做成函数 #define START_ADC_AIN(ch, prescale) \ do{ \ ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; \ ADCCON |= ADC_START; \ }while(0) ;ADC 中断处理函数 static irqreturn_t adcdone_int_handler(int irq, void *dev_id)

{ ;如果 ADC 驱动拥有“A/D 转换器”资源,则从 ADC 寄存器读取转换结果 if (OwnADC) { adc_data = ADCDAT0 & 0x3ff; ev_adc = 1; wake_up_interruptible(&adcdev.wait); } return IRQ_HANDLED; } ;ADC 读函数,一般对应于用户层/应用层的设备读函数(read) static ssize_t s3c2410_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos) { char str[20]; int value; size_t len; ;判断“A/D 转换器”资源是否可用 if (down_trylock(&ADC_LOCK) == 0) { OwnADC = 1; //标记“A/D 转换器”资源状态为可用 START_ADC_AIN(adcdev.channel, adcdev.prescale); //开始转换 wait_event_interruptible(adcdev.wait, ev_adc); //通过终端的方式等待转换结果 ev_adc = 0; DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ADCCON & 0x80 ? 1:0); ;把转换结果赋予 value,以便传递到用户层/应用层 value = adc_data; ;释放“A/D 转换器”资源 OwnADC = 0; up(&ADC_LOCK); } else { ;没有“A/D 转换器”资源,赋值为“-1” value = -1; }

len = sprintf(str, "%d\n", value); if (count >= len) { ;把转换结果传递到用户层/应用层 int r = copy_to_user(buffer, str, len); return r ? r : len; } else { return -EINVAL; } } ;打开 ADC 设备的函数,一般对应于用户态程序的 open static int s3c2410_adc_open(struct inode *inode, struct file *filp) { ;初始化中断队列 init_waitqueue_head(&(adcdev.wait)); ;缺省通道为“0” adcdev.channel=0; adcdev.prescale=0xff; DPRINTK( "adc opened\n"); return 0; } static int s3c2410_adc_release(struct inode *inode, struct file *filp) { DPRINTK( "adc closed\n"); return 0; } static struct file_operations dev_fops = { owner: THIS_MODULE, open: s3c2410_adc_open, read:s3c2410_adc_read, release: s3c2410_adc_release, };

static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; static int __init dev_init(void) { int ret; base_addr=ioremap(S3C2410_PA_ADC,0x20); if (base_addr == NULL) { printk(KERN_ERR "Failed to remap register block\n"); return -ENOMEM; } adc_clock = clk_get(NULL, "adc"); if (!adc_clock) { printk(KERN_ERR "failed to get adc clock source\n"); return -ENOENT; } clk_enable(adc_clock); /* normal ADC */ ADCTSC = 0; ;注册中断 ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev); if (ret) { iounmap(base_addr); return ret; } ;注册设备 ret = misc_register(&misc); printk (DEVICE_NAME"\tinitialized\n"); return ret; }

static void __exit dev_exit(void) { ;释放中断 free_irq(IRQ_ADC, &adcdev); iounmap(base_addr); if (adc_clock) { clk_disable(adc_clock); clk_put(adc_clock); adc_clock = NULL; } misc_deregister(&misc); } ;导出信号量“ADC_LOCK”,以便触摸屏驱动使用 EXPORT_SYMBOL(ADC_LOCK); module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("FriendlyARM Inc.");

上面的驱动程序中还包含了一个简单的头文件“s3c24xx-adc.h”,它也在 drivers/char目录下,内容为: #ifndef _S3C2410_ADC_H_ #define _S3C2410_ADC_H_ #define ADC_WRITE(ch, prescale) ((ch)<<16|(prescale)) #define ADC_WRITE_GETCH(data) (((data)>>16)&0x7) #define ADC_WRITE_GETPRE(data) ((data)&0xff) #endif /* _S3C2410_ADC_H_ */

然后打开 drivers/char/Makefile 文件,在大概 114 行加入 ADC 驱动程序目标模块: obj-$(CONFIG_JS_RTC) += js-rtc.o js-rtc-y = rtc.o obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o

# Files generated that shall be removed upon make clean clean-files := consolemap_deftbl.c defkeymap.c

再打开 drivers/char/Kconfig 文件,加入 ADC 驱动配置选项: config DEVKMEM bool "/dev/kmem virtual device support" default y help Say Y here if you want to support the /dev/kmem device. The /dev/kmem device is rarely used, but can be used for certain kind of kernel debugging operations. When in doubt, say "N". config MINI2440_ADC bool "ADC driver for FriendlyARM Mini2440 development boards" depends on MACH_MINI2440 default y if MACH_MINI2440 help this is ADC driver for FriendlyARM Mini2440 development boards Notes: the touch-screen-driver required this option config BFIN_JTAG_COMM tristate "Blackfin JTAG Communication" depends on BLACKFIN help

这样,我们就在内核中添加了 ADC 驱动,现在内核源代码目录的命令行执行:make menuconfig,依次选择如下子菜单项,找到刚刚添加的 ADC 驱动配置选项: Device Drivers ---> Character devices --->

如图所示,按空格键选中 ADC 配置选项

然 后 退 出 保 存 所 选 配 置 , 在 命 令 行 执 行 : make zImage , 将 会 生 成

arch/arm/boot/zImage,使用 supervivi 的“k”命令把它烧写到开发板。

3.14.3 ADC 测试程序

我们在这里使用友善之臂自带的文件系统,里面有一个 adc-test 命令,它的源代码如

下: #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #include <linux/fs.h> #include <errno.h> #include <string.h> int main(void) {

fprintf(stderr, "press Ctrl-C to stop\n"); int fd = open("/dev/adc", 0); if (fd < 0) { perror("open ADC device:"); return 1; } for(;;) { char buffer[30]; int len = read(fd, buffer, sizeof buffer -1); if (len > 0) { buffer[len] = '\0'; int value = -1; sscanf(buffer, "%d", &value); printf("ADC Value: %d\n", value); } else { perror("read ADC device:"); return 1; } usleep(500* 1000); } close(fd); }

“adc-test”测试程序已经集成到我们的文件系统中,因此在开发板的命令行终端输入:

adc-test,旋转开发板上的 W1 可调电阻,可以看到 ADC 转换的结果也在变动,按下触摸屏时,

会输出“-1”,这和我们在驱动程序中设置的结果是一样的,如图:

3.15 添加触摸屏驱动(带详细原理分析)

3.15.1 在内核中添加触摸屏驱动程序

Linux-2.6.32.2 内核也没有包含支持 S3C2440 的触摸屏驱动,因此我们自行设计了一个

s3c2410_ts.c,它位于 linux-src/drivers/input/touchscreen 目录下,你可以自己增加一个

s3c2410_ts.c 文件,并复制如下内容: #include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/init.h> #include <linux/serio.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/clk.h>

#include <linux/gpio.h> #include <asm/io.h> #include <asm/irq.h> #include <plat/regs-adc.h> #include <mach/regs-gpio.h> /* For ts.dev.id.version */ #define S3C2410TSVERSION 0x0101 #define WAIT4INT(x) (((x)<<8) | \ S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \ S3C2410_ADCTSC_XY_PST(3)) #define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \ S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0)) static char *s3c2410ts_name = "s3c2410 TouchScreen"; static struct input_dev *dev; static long xp; static long yp; static int count; extern struct semaphore ADC_LOCK; static int OwnADC = 0; static void __iomem *base_addr; static inline void s3c2410_ts_connect(void) { s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPG12_XMON); s3c2410_gpio_cfgpin(S3C2410_GPG(13), S3C2410_GPG13_nXPON); s3c2410_gpio_cfgpin(S3C2410_GPG(14), S3C2410_GPG14_YMON); s3c2410_gpio_cfgpin(S3C2410_GPG(15), S3C2410_GPG15_nYPON); }

static void touch_timer_fire(unsigned long data) { unsigned long data0; unsigned long data1; int updown; data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); if (updown) { if (count != 0) { long tmp; tmp = xp; xp = yp; yp = tmp; xp >>= 2; yp >>= 2; input_report_abs(dev, ABS_X, xp); input_report_abs(dev, ABS_Y, yp); input_report_key(dev, BTN_TOUCH, 1); input_report_abs(dev, ABS_PRESSURE, 1); input_sync(dev); } xp = 0; yp = 0; count = 0; iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC); iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else { count = 0;

input_report_key(dev, BTN_TOUCH, 0); input_report_abs(dev, ABS_PRESSURE, 0); input_sync(dev); iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); if (OwnADC) { OwnADC = 0; up(&ADC_LOCK); } } } static struct timer_list touch_timer = TIMER_INITIALIZER(touch_timer_fire, 0, 0); static irqreturn_t stylus_updown(int irq, void *dev_id) { unsigned long data0; unsigned long data1; int updown; if (down_trylock(&ADC_LOCK) == 0) { OwnADC = 1; data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); if (updown) { touch_timer_fire(0); } else { OwnADC = 0; up(&ADC_LOCK); } } return IRQ_HANDLED;

} static irqreturn_t stylus_action(int irq, void *dev_id) { unsigned long data0; unsigned long data1; if (OwnADC) { data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK; count++; if (count < (1<<2)) { iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC); iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else { mod_timer(&touch_timer, jiffies+1); iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC); } } return IRQ_HANDLED; } static struct clk *adc_clock; static int __init s3c2410ts_init(void) { struct input_dev *input_dev; adc_clock = clk_get(NULL, "adc"); if (!adc_clock) { printk(KERN_ERR "failed to get adc clock source\n");

return -ENOENT; } clk_enable(adc_clock); base_addr=ioremap(S3C2410_PA_ADC,0x20); if (base_addr == NULL) { printk(KERN_ERR "Failed to remap register block\n"); return -ENOMEM; } /* Configure GPIOs */ s3c2410_ts_connect(); iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\ base_addr+S3C2410_ADCCON); iowrite32(0xffff, base_addr+S3C2410_ADCDLY); iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); /* Initialise input stuff */ input_dev = input_allocate_device(); if (!input_dev) { printk(KERN_ERR "Unable to allocate the input device !!\n"); return -ENOMEM; } dev = input_dev; dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS); dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH); input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0); dev->name = s3c2410ts_name; dev->id.bustype = BUS_RS232; dev->id.vendor = 0xDEAD; dev->id.product = 0xBEEF; dev->id.version = S3C2410TSVERSION;

/* Get irqs */ if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM, "s3c2410_action", dev)) { printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !\n"); iounmap(base_addr); return -EIO; } if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c2410_action", dev)) { printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n"); iounmap(base_addr); return -EIO; } printk(KERN_INFO "%s successfully loaded\n", s3c2410ts_name); /* All went ok, so register to the input system */ input_register_device(dev); return 0; } static void __exit s3c2410ts_exit(void) { disable_irq(IRQ_ADC); disable_irq(IRQ_TC); free_irq(IRQ_TC,dev); free_irq(IRQ_ADC,dev); if (adc_clock) { clk_disable(adc_clock); clk_put(adc_clock); adc_clock = NULL; } input_unregister_device(dev); iounmap(base_addr); }

module_init(s3c2410ts_init); module_exit(s3c2410ts_exit);

然后在 linux-2.6.32.2/drivers/input/touchscreen/Makefile 文件中添加该源代码的目标

模块,如图红色部分: obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o

再打开 linux-2.6.32.2/drivers/input/touchscreen/Kconfig 文件,加入如下红色部分,这

样就在内核配置中添加了 mini2440 的触摸屏驱动选项: menuconfig INPUT_TOUCHSCREEN bool "Touchscreens" help Say Y here, and a list of supported touchscreens will be displayed. This option doesn't affect the kernel. If unsure, say Y. if INPUT_TOUCHSCREEN config TOUCHSCREEN_S3C2410 tristate "Samsung S3C2410 touchscreen input driver" depends on MACH_MINI2440 && INPUT && INPUT_TOUCHSCREEN && MINI2440_ADC help Say Y here if you have the s3c2410 touchscreen. If unsure, say N. To compile this driver as a module, choose M here: the module will be called s3c2410_ts. config TOUCHSCREEN_ADS7846 tristate "ADS7846/TSC2046 and ADS7843 based touchscreens" depends on SPI_MASTER depends on HWMON = n || HWMON help

至此,我们就已经在内核中添加完了触摸屏驱动。

3.15.2 配置编译内核并测试触摸屏驱动

在命令行执行:make menuconfig,然后依次选择如下子菜单,找到刚刚添加的触摸

屏驱动选项: Device Drivers ---> Input device support --->

[*] Touchscreens ---> 如图所示,按空格键选中触摸屏驱动配置选项:

退出并保存以上内核配置,在命令行输入:make zImage,将生成 arch/arm/boot/zImage

文件,使用 supervivi 的“k”命令把它烧写到开发板。

在此我们还是使用缺省的文件系统 root_qtopia,可以看到屏幕上出现校正界面:

依照屏幕提示,使用触摸笔逐步点击“十”型交叉点,即可进入 qtopia 系统。

3.15.3 触摸屏驱动原理详解

或许你对触摸屏驱动程序还不是很理解,有一个网友恰好详细的分析了触摸屏驱动的

原理,现摘录如下:

以下内容来自链接(里面包含作者,但原文出处我们无处得知,如果作者看到此文档,

请和我们联系,我们将会改正链接):

原文出处:

http://www.arm9home.net/read.php?tid-2406-keyword-%B4%A5%C3%FE%C6%C1%B3%CC%D0%F2.html

mini2440 触摸屏驱动程序分析

By JeefJiang July,8th,2009

这是 mini2440 驱动分析系列的第三篇文章,本文分为三个部分,第一部分讲叙硬件知识,包括触摸屏

的原理以及 SCC2440 SOC 上的触摸屏是如何工作的。第二部分分析输入设备子系统的框架,并进行相应

的代码分析。第三部分利用上述的原理来分析 mini2440 的触摸屏驱动。第四部分介绍了测试和校准。 1.需要准备的硬件知识 1.1 电阻式触摸屏工作原理原理 触摸屏附着在显示器的表面,与显示器相配合使用,如果能测量出触摸点在屏幕上的坐标位置,则可根

据显示屏上对应坐标点的显示内容或图符获知触摸者的意图。触摸屏按其技术原理可分为五类:矢量压力

传感式、电阻式、电容式、红外线式、表面声波式,其中电阻式触摸屏在嵌入式系统中用的较多。电阻触

摸屏是一块 4 层的透明的复合薄膜屏,如图 2 所示, 下面是玻璃或有机玻璃构成的基层, 上面是一层

外表面经过硬化处理从而光滑防刮的塑料层,中间是两层金属导电层,分别在基层之上和塑料层内表面,

在两导电层之间有许多细小的透明隔离点把它们隔开。当手指触摸屏幕时,两导电层在触摸点处接触。 触摸屏的两个金属导电层是触摸屏的两个工作面,在每个工作面的两端各涂有一条银胶,称为该工作面

的一对电极,若在一个工作面的电极对上施加电压,则在该工作面上就会形成均匀连续的平行电压分布。

如图 4 所示,当在 X 方向的电极对上施加一确定的电压,而 Y 方向电极对上不加电压时,在 X 平行电压场

中,触点处的电压值可以在 Y+(或 Y-)电极上反映出来,通过测量 Y+电极对地的电压大小,便可得知触点的

X 坐标值。同理,当在 Y 电极对上加电压,而 X 电极对上不加电压时,通过测量 X+电极的电压,便可得知

触点的 Y 坐标。电阻式触摸屏有四线和五线两种。四线式触摸屏的 X 工作面和 Y 工作面分别加在两个导电

层上,共有四根引出线,分别连到触摸屏的 X 电极对和 Y 电极对上。五线式触摸屏把 X 工作面和 Y 工作面

都加在玻璃基层的导电涂层上,但工作时,仍是分时加电压的,即让两个方向的电压场分时工作在同一工

作面上,而外导电层则仅仅用来充当导体和电压测量电极。因此,五线式触摸屏的引出线需为 5 根。 1.2 在 S3C2440 中的触摸屏接口 SOC S3C2440 的触摸屏接口是与 ADC 接口结合在一起的,框图如下: 转换速率:当 PCLK=50MHz 时,分频设为 49,则 10 位的转换计算如下: When the GCLK frequency is 50MHz and the prescaler value is 49, A/D converter freq. = 50MHz/(49+1) = 1MHz Conversion time = 1/(1MHz / 5cycles) = 1/200KHz = 5 us This A/D converter was designed to operate at maximum 2.5MHz clock, so the conversion rate can

go up to 500 KSPS. 触摸屏接口的模式有以下几种: 普通 ADC 转换模式 独立 X/Y 位置转换模式 自动 X/Y 位置转换模式 等待中断模式 我们主要接受触摸屏接口的等待中断模式和自动 X/Y 位置转换模式(驱动程序中会用到): 自动转换模式操作流程如下:触摸屏控制器自动转换 X,Y 的触摸位置,当转换完毕后将数据分别存放在

寄存器 ADCDAT0 和 ADCDAT1.并产生 INT_ADC 中断通知转换完毕。 等待中断模式: Touch Screen Controller generates interrupt (INT_TC) signal when the Stylus is down. Waiting for

Interrupt Modesetting value is rADCTSC=0xd3; // XP_PU, XP_Dis, XM_Dis, YP_Dis, YM_En. 当触摸后,触摸屏控制器产生 INT_TC 中断,四个引脚设置应该为: 引脚 XP XM YP YM 状态 PULL UP/XP Disable Disable (初始值即是) Disable Enable 设置 1 0 1 1 当中断产生后,X/Y 的位置数据可以选择独立 X/Y 位置转换模式,和自动 X/Y 位置转换模式进行读取,

采用自动 X/Y 位置转换模式进行读取需要对我们已经设置的 TSC 寄存器进行更改,在原有的基础上或上

S3C2410_ADCTSC_PULL_UP_DISABLE | S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0)。

数据转换完毕后,也会产生中断。 2. 输入子系统模型分析 2.1 整体框架: 输入子系统包括三个部分设备驱动、输入核心、事件处理器。 第一部分是连接在各个总线上的输入设备驱动,在我们的 SOC 上,这个总线可以使虚拟总线

platformbus,他们的作用是将底层的硬件输入转化为统一事件型式,向输入核心(Input core)汇报.

第二部分输入核心的作用如下: (1) 调用 input_register_device() used to 添加设备,调用 input_unregister_device() 除去设备。(下

面会结合触摸屏驱动讲述) (2) 在/PROC 下产生相应的设备信息,下面这个例子即是: /proc/bus/input/devices showing a USB mouse: I: Bus=0003 Vendor=046d Product=c002 Version=0120 N: Name="Logitech USB-PS/2 Mouse M-BA47" P: Phys=usb-00:01.2-2.2/input0 H: Handlers=mouse0 event2 B: EV=7 B: KEY=f0000 0 0 0 0 0 0 0 0 B: REL=103 (3) 通知事件处理器对事件进行处理 第三部分是事件处理器: 输入子系统包括了您所需要的大所属处理器,如鼠标、键盘、joystick,触摸屏,也有一个通用的处理

器被叫做 event handler(对于内核文件 evdev.C).需要注意的是随着内核版本的发展,event handler 将用

来处理更多的不同硬件的输入事件。在Linux2.6.29版本中,剩下的特定设备事件处理就只有鼠标和 joystick。这就意味着越来越多的输入设备将通过 event handler 来和用户空间打交道。事件处理层的主要作用就是和

用户空间打交道,我们知道 Linux 在用户空间将所有设备当成文件来处理,在一般的驱动程序中都有提供

fops 接口,以及在/dev 下生成相应的设备文件 nod,而在输入子系统的驱动中,这些动作都是在事件处理

器层完成的,我们看看 evdev.C 相关代码吧。 static int __init evdev_init(void) { return input_register_handler(&evdev_handler); } 这是该模块的注册程序,将在系统初始化时被调用。 初始化得过程很简单,就一句话,不过所有的秘密都被保藏在 evdev_handler 中了: static struct input_handler evdev_handler = { .event = evdev_event, .connect = evdev_connect, .disconnect = evdev_disconnect, .fops = &evdev_fops, .minor = EVDEV_MINOR_BASE, .name = "evdev", .id_table = evdev_ids, }; 先看 connect 函数中如下的代码: snprintf(evdev->name, sizeof(evdev->name), "event%d", minor); evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

evdev->handle.dev = input_get_device(dev); evdev->handle.name = evdev->name; dev_set_name(&evdev->dev, evdev->name); evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); evdev->dev.class = &input_class; evdev ->dev.parent = &dev->dev; evdev->dev.release = evdev_free; device_initialize(&evdev->dev); error = device_add(&evdev->dev); 注意黑色的部分这将会在/sys/device/viture/input/input0/event0 这个目录就是在这里生成的,在 event

下会有一个 dev 的属性文件,存放着设备文件的设备号,,这样 udev 就能读 取该属性文件获得设备号,从而在/dev 目录下创建设备节点/dev/event0 再看 evdev_fops 成员: static const struct file_operations evdev_fops = { .owner = THIS_MODULE, .read = evdev_read, .write = evdev_write, .poll = evdev_poll, .open = evdev_open, .release = evdev_release, .unlocked_ioctl = evdev_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = evdev_ioctl_compat, #endif .fasync = evdev_fasync, .flush = evdev_flush }; 看过 LDD3 的人都知道,这是设备提供给用户空间的接口,用来提供对设备的操作,其中 evdev_ioctl

提供了很多命令,相关的命令使用参照《Using the Input Subsystem, Part II》 3 mini2440 的触摸屏驱动 3.1 初始化: static int __init s3c2410ts_init(void)

{ struct input_dev *input_dev;

adc_clock = clk_get(NULL, "adc");

if (!adc_clock) { printk(KERN_ERR "failed to get adc clock source\n"); return -ENOENT; } clk_enable(adc_clock);

//获取时钟,挂载 APB BUS 上的外围设备,需要时钟控制,ADC 就是这样的设备。 base_addr=ioremap(S3C2410_PA_ADC,0x20); I/O 内存是不能直接进行访问的,必须对其进行映射,为 I/O 内存分配虚拟地址,这些虚拟地址以

__iomem 进行说明,但不能直接对其进行访问,需要使用专用的函数,如 iowrite32 if (base_addr == NULL) { printk(KERN_ERR "Failed to remap register block\n"); return -ENOMEM; }

/* Configure GPIOs */

s3c2410_ts_connect(); iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\ base_addr+S3C2410_ADCCON);//使能预分频和设置分频系数 iowrite32(0xffff, base_addr+S3C2410_ADCDLY);//设置 ADC 延时,在等待中断 模式下表示产生 INT_TC 的间隔时间

iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); 按照等待中断的模式设置 TSC 接下来的部分是注册输入设备 /* Initialise input stuff */ input_dev = input_allocate_device(); //allocate memory for new input device,用来给输入设备分配空间,并做一些输入设备通用的初始的设

置 if (!input_dev) { printk(KERN_ERR "Unable to allocate the input device !!\n"); return -ENOMEM; } dev = input_dev; dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS); //设置事件类型 dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH); input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0); 以上四句都是设置事件类型中的 code,如何理解呢,先说明事件类型,常用的事件类型 EV_KEY、

EV_MOSSE, EV_ABS(用来接收像触摸屏这样的绝对坐标事件),而每种事件又会有不同类型的编码 code,比方说 ABS_X,ABS_Y,这些编码又会有相应的 value

dev->name = s3c2410ts_name; dev->id.bustype = BUS_RS232; dev->id.vendor = 0xDEAD;

dev->id.product = 0xBEEF; dev->id.version = S3C2410TSVERSION; //以上是输入设备的名称和 id,这些信息时输入设备的身份信息了,在用户空间如何看到呢, cat /proc/bus/input/devices,下面是我的截图

/* Get irqs */ if (request_irq(IRQ_ADC, stylus_action, IRQF_SAMPLE_RANDOM, "s3c2410_action", dev)) { printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !\n"); iounmap(base_addr); return -EIO; } if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c2410_action", dev)) { printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n"); iounmap(base_addr); return -EIO; } printk(KERN_INFO "%s successfully loaded\n", s3c2410ts_name); /* All went ok, so register to the input system */ input_register_device(dev);

//前面已经设置了设备的基本信息和所具备的能力,所有的都准备好了,现在就可以注册了 return 0; } 中断处理 stylus_action 和 stylus_updown 两个中断处理函数,当笔尖触摸时,会进入到 stylus_updown, static irqreturn_t stylus_updown(int irq, void *dev_id) { unsigned long data0; unsigned long data1; int updown; //注意在触摸屏驱动模块中,这个 ADC_LOCK 的作用是保证任何时候都只有一个驱动程序使用 ADC 的

中断线,因为在 mini2440adc 模块中也会使用到 ADC,这样只有拥有了这个锁,才能进入到启动 ADC,注意

尽管 LDD3 中说过信号量因为休眠不适合使用在 ISR 中,但 down_trylock 是一个例外,它不会休眠。 if (down_trylock(&ADC_LOCK) == 0) { OwnADC = 1; data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 &

S3C2410_ADCDAT0_UPDOWN)); if (updown) {//means down touch_timer_fire(0);//这是一个定时器函数,当然在这里是作为普通函数调用,用来启动 ADC } else { OwnADC = 0; up(&ADC_LOCK);//注意红色的部分是基本不会执行的,除非你触摸后以飞快的速度是否,还来

不及启动 ADC,当然这种飞快的速度一般是达不到的,笔者调试程序时发现这里是进入不了的 } } return IRQ_HANDLED; } static void touch_timer_fire(unsigned long data) { unsigned long data0; unsigned long data1; int updown; data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1);

updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 &

S3C2410_ADCDAT0_UPDOWN)); if (updown) {//means down 转换四次后进行事件汇报 if (count != 0) { long tmp; tmp = xp; xp = yp; yp = tmp; //这里进行转换是因为我们的屏幕使用时采用的是 240*320,相当于把原来的屏幕的 X,Y 轴变换。

个人理解,不只是否正确 xp >>= 2; yp >>= 2; input_report_abs(dev, ABS_X, xp); input_report_abs(dev, ABS_Y, yp);

//设备 X,Y 值 input_report_key(dev, BTN_TOUCH, 1); input_report_abs(dev, ABS_PRESSURE, 1); input_sync(dev); //这个表明我们上报了一次完整的触摸屏事件,用来间隔下一次的报告 } xp = 0; yp = 0; count = 0; iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,

base_addr+S3C2410_ADCTSC); iowrite32(ioread32(base_addr+S3C2410_ADCCON) |

S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); 如果还没有启动 ADC 或者 ACD 转换四次完毕后则启动 ADC } else { 如果是 up 状态,则提出报告并让触摸屏处在等待触摸的阶段 count = 0; input_report_key(dev, BTN_TOUCH, 0);

input_report_abs(dev, ABS_PRESSURE, 0); input_sync(dev); iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); if (OwnADC) { OwnADC = 0; up(&ADC_LOCK); } } } static irqreturn_t stylus_action(int irq, void *dev_id) {

unsigned long data0; unsigned long data1;

if (OwnADC) { data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK; count++; 读取数据 if (count < (1<<2)) {如果小如四次重新启动转换 iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,

base_addr+S3C2410_ADCTSC); iowrite32(ioread32(base_addr+S3C2410_ADCCON) |

S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else {如果超过四次,则等待 1ms 后进行数据上报 mod_timer(&touch_timer, jiffies+1); iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC); } } return IRQ_HANDLED; } 我们从整体上描述转换的过程:

(1) 如果触摸屏感觉到触摸,则进入 updown ISR,如果能获取ADC_LOCK则调用 touch_timer_fire,启动 ADC,

(2) ADC 转换,如果小于四次继续转换,如果四次完毕后,启动 1 个时间滴答的定时器,停止 ADC, 也就是说在这个时间滴答内,ADC 是停止的,

(3) 这样可以防止屏幕抖动。 (4) 如果 1 个时间滴答到时候,触摸屏仍然处于触摸状态则上报转换数据,并重启 ADC,重复(2)(5) 如果触摸笔释放了,则上报释放事件,并将触摸屏重新设置为等待中断状态。 4 测试与校准 关于应用程序的编写,请参照《Using the Input Subsystem, Part II》,讲解了 input 设备的 API, 触摸屏的校准时使触摸屏的坐标与 LCD 得坐标进行对应,这种对应需要映射,这个映射的过程即为校

准,我们提供了一种线性算法的映射方法,具体的代码见附件。

3.16 配置 USB 外设

Linux-2.6.32.2内核对USB外设的支持是相当丰富的,并且已经包含了S3C2440的USB Host 驱动支持,因此我们只要配置一下内核就可以了,下面是各种 USB 外设的详细配置步骤。

3.16.1 配置和测试 USB 键盘、扫描器和鼠标

在内核源代码目录的终端输入:make menuconfig,依次选择如下子菜单项: Device Drivers ---> [*] HID Devices --->

出现如图内核配置菜单:

按空格键选中“USB Human Interface Device (full HID) support”,这样就配置好了 USB

键盘和鼠标项。 提示:这里的配置选项对应的内核源代码目录是:linux-2.6.32.2/drivers/hid/usbhid,其

中 USB 键盘和条码扫描器的原理是一样的,因此它们的代码是相同的。

3.16.2 测试 USB 键盘、扫描器和鼠标

在内核源代码根目录下执行:make zImage,把生成的新内核烧写到开发板中,我们这

里依然使用友善之臂提供的文件系统 root_qtopia 做测试,因为它可以同时支持 USB 键盘、鼠

标和触摸屏,并且是支持热插拔,所以使用起来十分方便。在前面的步骤中,我们通过触摸屏

点击已经进入了 qtopia 图形系统,因此这里直接找个 USB HUB,同时连上 USB 鼠标和键盘,

甚至是 USB 条码扫描器就可以了,使用鼠标找到一个应用程序,比如 qtopia 自带的“便签”,

点击打开它,这时就可以使用键盘输入各种英文字符了,还可以使用 USB 条码扫描器直接扫

描条码进行输入,如图:

3.16.3 配置优盘

因为优盘用到了 SCSI 命令,所以我们先增加 SCSI 支持。 在 Device Drivers 菜单里面,选择 SCSI device support,按回车进入

出现如图菜单,按空格键选择如图选项

返回 Device Drivers 菜单,再选择 USB support,按回车进入 USB support 菜单 找到并选中“<*> USB Mass Storage support”,如图

另外,现在的优盘等移动存储器使用的大都是 FAT/FAT32 格式的,因此我们还需要添

加 FAT32 文件系统的支持,在内核配置主菜单下依次选择如下菜单项:

File systems ---> DOS/FAT/NT Filesystems --->

进入 FAT32 文件系统配置子菜单,并选择如图:

除此之外,为了支持中英文的编码,在“File systems”菜单下选择“-*- Native language

support --->”并进入,如图

在这里,我们要选择如下几个编码的支持:

<*> Codepage 437 (United States, Canada) <*> NLS ISO 8859-1 (Latin 1; Western European Languages) <*> NLS UTF-8

退出保存以上配置。

3.16.4 测试优盘

接上面的步骤,在内核源代码根目录下执行:make zImage,把生成的新内核烧写到

开发板中,先不要插入优盘(这样做是为了看插入时的打印信息),等系统启动后,进入命令行

控制台,此时优盘,可以看到如下信息:

此时优盘已经被自动挂载到开发板的/udisk 目录,同时,在 Qtopia 系统中,可以看到

任务栏上多了一个图标,如图

优盘中的所有文件会在“文档”组中全部显示出来,但它是不显示目录名称的,如果

你的文件太多,那么其列表也是相当可观的。 说明:在 Qtopia 中支持优盘自动挂载是通过友善之臂开发的一个 Qtopia 2.2.0 插件实

现的,目前它只识别 MMC/SD 卡或优盘的第一个分区,并且格式为常见的 VFAT/FAT32/FAT16,如果你的优盘或者 SD 卡不能识别,请检查是否为 VFAT/FAT32/FAT16 格式。

3.16.5 配置和测试 USB 摄像头

Linux 内核版本之所以经常会有更新,其中不断加入更多的驱动支持是主要原因之一,

目录 Linux-2.6.32.2 已经支持几乎所有的 USB 摄像头驱动,但是每个厂家的 USB 摄像头驱动

对上层提供的接口各不相同,因此即使内核支持了这么多摄像头,普通的 USB 摄像头应用程

序也无法识别到它们,这正是我们在 Qtopia 的“USB 摄像头”about 说明中建议用户向我们反

馈信息的原因。下面是在内核中配置 USB 摄像头的步骤: 在 Device Drivers 菜单里面,选择 Multimedia devices,回车进入

选择如图“*”号选项,并选择 Video capture adapters 进入

出现如图菜单,找到如图选项并进入

出现如图菜单,选择如图“*”号选项,再选 GSPCA based webcams 进入

GSPCA 是一个法国程序员在业余时间制作的一个万能 USB 摄像头驱动程序,在此你

可以选择所有类型 USB 摄像头的支持,如图 --- GSPCA based webcams <*> ALi USB m5602 Camera Driver <*> STV06XX USB Camera Driver <*> GL860 USB Camera Driver <*> Conexant Camera Driver <*> Etoms USB Camera Driver <*> Fujifilm FinePix USB V4L2 driver <*> Jeilin JPEG USB V4L2 driver <*> Mars USB Camera Driver <*> Mars-Semi MR97310A USB Camera Driver <*> OV519 USB Camera Driver <*> OV534 USB Camera Driver <*> Pixart PAC207 USB Camera Driver <*> Pixart PAC7311 USB Camera Driver <*> SN9C20X USB Camera Driver <*> SONIX Bayer USB Camera Driver <*> SONIX JPEG USB Camera Driver <*> SPCA500 USB Camera Driver <*> SPCA501 USB Camera Driver

<*> SPCA505 USB Camera Driver <*> SPCA506 USB Camera Driver <*> SPCA508 USB Camera Driver <*> SPCA561 USB Camera Driver <*> SQ Technologies SQ905 based USB Camera Driver <*> SQ Technologies SQ905C based USB Camera Driver <*> Syntek DV4000 (STK014) USB Camera Driver <*> SUNPLUS USB Camera Driver <*> T613 (JPEG Compliance) USB Camera Driver <*> TV8532 USB Camera Driver <*> VC032X USB Camera Driver <*> ZC3XX USB Camera Driver

这样,我们就配置好了万能的 USB 摄像头驱动,如果有新的内核版本,或许还有很多

的选项。

3.16.6 测试 USB 摄像头

接上面的步骤,在内核源代码目录执行:make zImage,把生成的内核烧写到开发板

中,依然使用友善之臂自带的文件系统 root_qtopia,因为里面已经有一个 USB 摄像头动态预

览和拍照的程序(提示:你也可以参考“通过网络浏览 CMOS 或 USB 摄像头.pdf”一文实现网

页浏览控制 USB 摄像头)。 把摄像头插到开发板的 USB Host 端口,然后在“友善之臂”程序组中,点击打开“USB

摄像头”程序,你将会看到动态的预览界面,调节一下摄像头的对焦,拍好姿势,点下“Snap”按钮就可以拍照了,拍摄的照片将会保存到“文档”组中。

本程序还可以调节亮度、对比度和伽马值;每个型号的摄像头在出厂时已经设定了

佳值或者缺省值,程序开启时会读取它们并以此设定。

附: 为什么我的 USB 摄像头无法通过网络浏览? 但使用 mjpeg 软件通过网络控制和浏览 USB 摄像头时,有时会出现这样的信息:

[root@FriendlyARM /mjpg-streamer]# ./start_uvc_yuv.sh MJPG Streamer Version.: 2.0 i: Using V4L2 device.: /dev/video0 i: Desired Resolution: 640 x 480 i: Frames Per Second.: 5 i: Format............: YUV i: JPEG Quality......: 80 Pixel format is unavailable, using JPEG should never arrive exit fatal !! i: init_VideoIn failed

这说明,mjpeg 软件并不支持你所使用的 USB 摄像头类型。解决方法有两种: (1) 你可以更改其他型号的 USB摄像头试试 (2) 自行更改 mjpeg源代码以支持你所使用的 USB摄像头驱动程序接口 解释:虽然 Linux-2.6.32 等高版本的内核已经支持“万能”USB 摄像头驱动,但每个

驱动向上层提供的图像解码接口是不同的,而 mjpeg 软件仅能识别一部分型号的 USB 摄像头,

我们提供的 Qtopia 版本的 USB 摄像头动态预览程序,则根据用户的不断反馈增加了更多的

USB 摄像头向上层提供的图像解码接口支持,这也就是我们为什么会在该程序中添加了这样

一个提示:

目的就是让用户把所使用的 USB 摄像头样品寄给我们,以便在我们的程序中增加该型

号 USB 摄像头驱动中的图像解码支持。对于 mjpeg 软件,我们则不提供这样的升级改进,因

为 mjpeg 软件并不是我们开发和维护的。

3.16.7 配置和测试 USB 无线网卡

新的 Linux-2.6.32.2 内核不仅支持超多的 USB 摄像头驱动,还支持更多的 USB 无线网

卡,下面是添加 USB 无线网卡的详细步骤: 先添加无线网络协议,在主菜单中,选择 Netwoking support,回车进入

出现如图子菜单,如图选择 Wireless 并进入开始配置无线网络协议

选择如图“*”各项配置

退回到内核配置主菜单,选择 Device Drivers 并进入,开始配置无线网卡驱动,如图

进入网络设备子菜单,找到如图无线网络设备子项,并进入

再选择进入“[*] Wireless LAN (IEEE 802.11) ---> ”子菜单,在这里,你可以选

择配置各种型号的 USB 无线网卡,在此就不一一列举了,你可以根据自己的实际情况自行选

择测试,其中我们光盘中移植好了 Linux-2.6.32.2 内核也已经做了尽可能多的选择。

3.16.8 测试 USB 无线网卡

3.16.9 配置 USB 转串口

3.16.10 测试 USB 转串口

3.17 移植 SD 卡驱动

3.17.1 在内核中注册 SD 设备驱动

Linux-2.6.32.2 已经自带了 S3C2440 芯片的 SD 卡驱动,我们只需在初始化代码中加入

SD 平台设备结构就可以,打开 arch/arm/mach-s3c2440/mach-mini2440.c,在 nand flash 平台结

构后面添加如下红色代码: ;在 mini2440.c 的顶部添加 SD 卡设备结构所需的头文件 #include <linux/mmc/host.h> #include <plat/mci.h> static struct platform_device mini2440_device_eth = { .name = "dm9000", .id = -1, .num_resources = ARRAY_SIZE(mini2440_dm9k_resource), .resource = mini2440_dm9k_resource, .dev = { .platform_data = &mini2440_dm9k_pdata, }, }; /* MMC/SD */ static struct s3c24xx_mci_pdata mini2440_mmc_cfg = { .gpio_detect = S3C2410_GPG(8), .gpio_wprotect = S3C2410_GPH(8), .set_power = NULL, .ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34, };

并把 SD 卡结构设备添加到目标平台设备集中,如图: static struct platform_device *mini2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_rtc, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &mini2440_device_eth, &s3c24xx_uda134x, &s3c_device_nand, &s3c_device_sdi, };

SD 卡的驱动程序底层操作实际对应源代码 linux-2.6.32.2/drivers/mmc/host/s3cmci.c,根据测试,当包含内核打印信息时,SD 卡可以被正常识别使用,而没有打印信息时,则表现

的不太稳定,因此我们在该程序中添加了一句延时代码,如图: ;延时函数所需的头文件 #include <linux/delay.h> static void pio_tasklet(unsigned long data) { struct s3cmci_host *host = (struct s3cmci_host *) data; s3cmci_disable_irq(host, true); udelay(50); //在此处添加了延时函数 if (host->pio_active == XFER_WRITE) do_pio_write(host);

这样,我们就完成了 SD 卡驱动的移植。

3.17.2 测试 SD 卡

接上面的步骤,在内核源代码目录执行:make zImage,把生成的内核烧写到开发板

中,先不要插入 SD 卡(这样做是为了看插入时的打印信息),等系统启动后,进入命令行控制

台,此时插入 SD 卡,可以看到如下信息:

此时 SD 卡已经被自动挂载到开发板的/sdcard 目录,同时,在 Qtopia 系统中,可以看

到任务栏上多了一个图标,如图

SD 卡或者优盘中的所有文件会在“文档”组中全部显示出来,但它是不显示目录名称

的,如果你的文件太多,那么其列表也是相当可观的。 说明:支持 SD 卡或者优盘自动挂载是通过友善之臂开发的一个 Qtopia 2.2.0 插件实现

的,目前它只识别 MMC/SD 卡或优盘的第一个分区,并且格式为常见的 VFAT/FAT32/FAT16,如果你的优盘或者 SD 卡不能识别,请检查是否为 VFAT/FAT32/FAT16 格式。

3.17.3 mini2440 的 SD 卡驱动分析(来自网络)

说明:Mini2440 的 SD 卡驱动比较复杂,有网友对此进行了详细的分析,我们在此摘

录如下,在此十分感谢该网友的分享和贡献。 注意:原文视角并不代表友善之臂的观点。 原文出处:http://www.arm9home.net/read.php?tid-2570.html 注:本文有可能经多次转载,如原作者发现链接不符,请与我们联系。

mini2440 驱动分析系列之-Mini2440 SD 卡驱动程序分析参考资料: By JeefJiang July,25th,2009 Email:[email protected]

(本文仅做学习交流用,转载或引用请标明) 前几天把 mini2440 的 sd 卡驱动程序移植到了 Android 平台,当时对 SD 卡以及内核的 MMC 子系统不

是很了解,浏览了四天的代码,终于理清了一些头绪,尽管很多细节的实现还不是很清楚,不过先把知道

的记录下来,细节部分由时间在慢慢挖掘。本文先介绍了一下 MMC 的基本框架结构,然后采用自底向上

的方法来分析整个 MMC 层是如何共同作用的。阅读时请结合参考资料 1 和 2. 参考资料: 1.SD Memory Card Specifications / Part 1. Physical Layer Specification; Version 1.0 2.LDD3 CHAPTER-16 BLOCK DEVICE 3. http://www.sdcard.org

1.硬件基础

http://blog.ednchina.com/yelov/198217/message.aspx

2.MMC 子系统的基本框架结构

很遗憾,内核没有为我们提供关于 MMC 子系统的文档,在谷歌上搜索了很多,也没有找到相关文章。

只能自己看代码分析了,可能有很多理解不对的地方,希望研究过这方面的朋友多邮件交流一下。 MMC 子系统的代码在 kernel/driver/MMC 下,目前的 MMC 子系统支持一些形式的记忆卡:

SD,SDIO,MMC.由于笔者对 SDIO 的规范不是很清楚,后面的分析中不会涉及。MMC 子系统范围三个部分:

HOST 部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。

CORE 部分:这是整个 MMC 的核心存,这部分完成了不同协议和规范的实现,并为 HOST 层的驱动提

供了接口函数。 CARD 部分:因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你

的 SD 卡如何实现为块设备的。

3.HOST 层分析

HOST 层实现的就是我们针对特定主机的驱动程序,这里以 mini2440 的 s3cmci.c 为例子进行分析,我

们先采用 platform_driver_register(&s3cmci_2440_driver)注册了一个平台设备,接下来重点关注 probe 函数。

在这个函数总,我们与 CORE 的联系是通过下面三句实现的。首先分配一个 mmc_host 结构体,注意

sizeof(struct s3cmci_host),这样就能在 mmc_host 中找到了 s3cmci_host,嵌入结构和被嵌入的结构体能够找

到对方在 Linux 内核代码中的常用技术了。接下来为 mmc->pos 赋值,s3cmci_ops 结构实现了几个很重要的

函数,待会我一一介绍。中间还对 mmc 结构的很多成员进行了赋值, 后将 mmc 结构加入到 MMC 子系

统,mmc_alloc_host,以及 mmc_add_host 的具体做了什么事情,我们在下节再分析,这三句是些 MMC 层

驱动必须包含的。 mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); mmc->ops = &s3cmci_ops; …………… s3cmci_ops 中包含了四个函数: static struct mmc_host_ops s3cmci_ops = { .request = s3cmci_request, .set_ios = s3cmci_set_ios, .get_ro = s3cmci_get_ro, .get_cd = s3cmci_card_present, }; 我们从简单的开始分析,这些函数都会在 core 部分被调用: s3cmci_get_ro:这个函数通过从 GPIO 读取,来判断我们的卡是否是写保护的 s3cmci_card_present:这个函数通过从 GPIO 读取来判断卡是否存在 s3cmci_set_ios:s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) 依据核心层传递过来的 ios,来设置硬件 IO,包括引脚配置,使能时钟,和配置总线带宽。 s3cmci_request:这个函数是 主要,也 复杂的函数,实现了命令和数据的发送和接收, 当 CORE 部分需要发送命令或者传输数据时,都会调用这个函数,并传递 mrq 请求。 static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct s3cmci_host *host = mmc_priv(mmc); host->status = "mmc request"; host->cmd_is_stop = 0; host->mrq = mrq; if (s3cmci_card_present(mmc) == 0) { dbg(host, dbg_err, "%s: no medium present\n", __func__); host->mrq->cmd->error = -ENOMEDIUM; mmc_request_done(mmc, mrq);//如果卡不存在,就终止请求

} else s3cmci_send_request(mmc); } 接下来看 s3cmci_send_request(mmc): 这个函数先判断一下请求时传输数据还是命令,如果是数据的话: 先调用 s3cmci_setup_data 来对 S3C2410_SDIDCON 寄存器进行设置,然后设置 SDITIMER 寄存器这就

设置好了总线宽度,是否使用 DMA,,并启动了数据传输模式,并且使能了下面这些中断: imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC | S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH; 解析来判断是否是采用 DMA 进行数据传输还是采用 FIFO 进行数据传输 if (host->dodma) / because host->dodma = 0,so we don't use it res = s3cmci_prepare_dma(host, cmd->data);//准备 DMA 传输, else res = s3cmci_prepare_pio(host, cmd->data);.//准备 FIFO 传输 如果是命令的话:则调用 s3cmci_send_command()这个函数是命令发送的函数,和 datesheet 上描述

的过程差不多,关于 SD 规范中命令的格式,请参考参考资料 1. writel(cmd->arg, host->base + S3C2410_SDICMDARG);/*先写参数寄存器 ccon = cmd->opcode & S3C2410_SDICMDCON_INDEX;//确定命令种类 ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART; /*with start 2bits*/ if (cmd->flags & MMC_RSP_PRESENT) ccon |= S3C2410_SDICMDCON_WAITRSP; /*wait rsp*/ if (cmd->flags & MMC_RSP_136) ccon |= S3C2410_SDICMDCON_LONGRSP; //确定 respose 的种类 writel(ccon, host->base + S3C2410_SDICMDCON); 命令通道分析完了,我们分析数据通道,先分析采用 FIFO 方式传输是怎么样实现的。 先分析 s3cmci_prepare_pio(host, cmd->data) 根据 rw 来判断是读还是写 if (rw) { do_pio_write(host); /* Determines SDI generate an interrupt if Tx FIFO fills half*/ enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); } else { enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST);

} 如果是写数据到 SD 的话,会调用 do_pio_write,往 FIFO 中填充数据。当 64 字节的 FIFO 少于 33 字节

时就会产生中断。如果是从 SD 读数据,则先使能中断,当 FIFO 多于 31 字节时时,则会调用中断服务程

序,中断服务程序中将会调用 do_pio_read FIFO 的数据读出。 接下来分析 do_pio_write: to_ptr = host->base + host->sdidata; fifo_free(host)用来检测 fifo 剩余空间 while ((fifo = fifo_free(host)) > 3) { if (!host->pio_bytes) { res = get_data_buffer(host, &host->pio_bytes, /* If we have reached the end of the block, we have to * write exactly the remaining number of bytes. If we * in the middle of the block, we have to write full * words, so round down to an even multiple of 4. */ if (fifo >= host->pio_bytes)//fifo 的空间比 pio_bytes 大,表明这是读这个块的 后一次 fifo = host->pio_bytes; /* because the volume of FIFO can contain the remaning block*/ else fifo -= fifo & 3;/*round down to an even multiple of 4*/ host->pio_bytes -= fifo;//更新还剩余的没有写完的字 host->pio_count += fifo;/*chang the value of pio_bytes*/ fifo = (fifo + 3) >> 2;//将字节数转化为字数 /*how many words fifo contain,every time we just writ one word*/ ptr = host->pio_ptr; while (fifo--) writel(*ptr++, to_ptr);//写往 FIFO. host->pio_ptr = ptr; } 注释一:注意,MMC 核心为 mrq->data 成员分配了一个 struct scatterlist 的表,用来支持分散聚集,使

用这种方法,这样使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题 我们看代码 if (host->pio_sgptr >= host->mrq->data->sg_len) { dbg(host, dbg_debug, "no more buffers (%i/%i)\n", host->pio_sgptr, host->mrq->data->sg_len); return -EBUSY; } sg = &host->mrq->data->sg[host->pio_sgptr];

*bytes = sg->length;//页缓冲区中的长度 *pointer = sg_virt(sg);将页地址映射为虚拟地址 host->pio_sgptr++;这里表明我们的程序又完成了一次映射 这样,每一个 mmc 请求,我们只能处理 scatterlist 表中的一个页(块)。因此,完成一次完整的请求需

要映射 sg_len 次 再来总结一下一个 mmc 写设备请求的过程: 在 s3cmci_prepare_pio中我们第一次先调用do_pio_write,如果FIFO空间大于3,且能够获取到 scatterlist,

则我们就开始往 FIFO 写数据,当 FIFO 空间小于 3,则使能 TXFIFOHALF 中断,在中断服务程序中,如果

检测到 TFDET 表明又有 FIFO 空间了,则关闭 TXFIFOHALF 中断,并调用 do_pio_write 进行写。 数据流向如下:scatterlist-------->fifo---------->sdcard 一个 mmc 读设备请求的过程数据流向如下:sdcard --------> fifo ---------->scatterlist, ????关于读数据的过程,中断的触发不是很清楚,s3cmci_prepare_pio 中 enable_imask(host,

S3C2410_SDIIMSK_RXFIFOHALF,S3C2410_SDIIMSK_RXFIFOLAST);但如果没从 SD 卡中读数据,怎么

会引发这个中断呢?是由 S3C2410_SDIIMSK_RXFIFOLAST 引起的吗 接下来我们分析一下中断服务程序: static irqreturn_t s3cmci_irq(int irq, void *dev_id) 该程序先获取所有的状态寄存器: mci_csta = readl(host->base + S3C2410_SDICMDSTAT); mci_dsta = readl(host->base + S3C2410_SDIDSTA); mci_dcnt = readl(host->base + S3C2410_SDIDCNT); mci_fsta = readl(host->base + S3C2410_SDIFSTA); mci_imsk = readl(host->base + host->sdiimsk); 这些将作为中断处理的依据。 如果不是 DMA 模式,则处理数据的收发 if (!host->dodma) { if ((host->pio_active == XFER_WRITE) && (mci_fsta & S3C2410_SDIFSTA_TFDET)) { /*This bit indicates that FIFO data is available for transmit when DatMode is data transmit mode. If DMA mode is enable, sd host requests DMA operation.*/ disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); tasklet_schedule(&host->pio_tasklet); 注意我们采用 tasklet 这种延时机制来减少中断服务的时间,延时函数 pio_tasklet 中调用了 do_pio_write

和了 do_pio_read host->status = "pio tx"; } if ((host->pio_active == XFER_READ) && (mci_fsta & S3C2410_SDIFSTA_RFDET)) {

disable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST); tasklet_schedule(&host->pio_tasklet); host->status = "pio rx"; } 接下来的很多代码是对其他的一些类型中断的处理。 后来分析 DMA 模式:这种模式下不需要 CPU 的干预。S3C2440 的 DMA 有 4 个通道,我们选择了

通道 0 static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data) { int dma_len, i; int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0; BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR); s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);//注一 s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);//注二 if (dma_len == 0) return -ENOMEM; host->dma_complete = 0; host->dmatogo = dma_len; for (i = 0; i < dma_len; i++) { int res; dbg(host, dbg_dma, "enqueue %i:%u@%u\n", i, sg_dma_address(&data->sg), sg_dma_len(&data->sg)); res = s3c2410_dma_enqueue(host->dma, (void *) host, sg_dma_address(&data->sg),

sg_dma_len(&data->sg)); if (res) { s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); return -EBUSY; } } s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START); return 0; } 注一:这个函数先调用 s3c2410_dma_devconfig 来配置 DMA 源/目的的意见类型和地址,注意我们这里

的设备地址 host->mem->start + host->sdidata 实际上就是 SDIDATA 寄存器的地址值,如果是写 SD 卡,则为

目的地址,否则为源地址。然后调用 s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback); 设置 dma 通道 0 的回调函数。 注二: dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); 这里进行分散/聚集映射(P444,LDD3),返回值是传送的 DMA 缓冲区数,可能会小于 sg_len,也就是

说 sg_len 与 dma_len 可能是不同的。 sg_dma_address(&data->sg),返回的是总线(DMA)地址 sg_dma_len(&data->sg)); 返回的是缓冲区的长度。 后调用 s3c2410_dma_enqueue(host->dma, (void *) host,

sg_dma_address(&data->sg), sg_dma_len(&data->sg)); 对每个 DMA 缓冲区进行排队,等待处理。 s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);启动 DMA 这样 DMA 缓冲区就和 scatterlist 联系起来,当写数据时,scatterlist 中的数据由于上面的映射关系会直

接“拷贝”到 DMA 缓冲区,当读数据时则反之。整个过程不需要 CPU 干预,自动完成。

4.CORE 层分析

CORE 层完成了不同协议和规范的实现,并为 HOST 层的驱动提供了接口函数,在 HOST 层我们曾经

调用的两个函数: mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); mmc_add_host(mmc); 我们就从这两个函数入手,来分析 CORE 层与 HOST 层是如何交互的。 先看 mmc_alloc_host 函数:

dev_set_name(&host->class_dev, "mmc%d", host->index); host->parent = dev; host->class_dev.parent = dev; host->class_dev.class = &mmc_host_class; device_initialize(&host->class_dev); 这几句是将导致在/SYS/CLASS/mmc_host 下出现 mmc0 目录,添加类设备,在 2.6.21 后的版本中,类

设备的 class_device 已近被 device 所取代,LDD3P387 的内容有点 OUT 了 INIT_DELAYED_WORK(&host->detect, mmc_rescan); 初始化了一个工作队列,延时函数为 mmc_rescan,这个延时函数很重要,下午要详细分析 后对 host 做一些默认配置,不过这些配置在 probe 函数的后面都被重置了。

分析 mmc_add_host(mmc); device_add(&host->class_dev);这里才真正的添加了类设备。 其中调用了 mmc_start_host void mmc_start_host(struct mmc_host *host) { mmc_power_off(host); mmc_detect_change(host, 0); } mmc_power_off 中对 ios 进行了设置,然后调用 mmc_set_ios(host); host->ios.power_mode = MMC_POWER_OFF; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; mmc_set_ios(host); mmc_set_ios(host)中的关键语句 host->ops->set_ios(host, ios);这里的 set_ios 实际上就是我们前面所提到

的.set_ios = s3cmci_set_ios, 再看 mmc_detect_change(host, 0); 后一句是 mmc_schedule_delayed_work(&host->detect, delay); 实际上就是调用我们前面说的延时函数 mmc_rescan mmc_power_up(host);//这个函数实际上与前面的 mmc_power_off 类似,不过设置了启动时需要的 ios mmc_go_idle(host); //CMD0 ,from inactive to idle mmc_send_if_cond(host, host->ocr_avail);//发送 SD_SEND_IF_COND,是使用 SD2.0 卡才需要

设置的命令 /*suppot for 2.0 card*/ * ...then normal SD... */ err = mmc_send_app_op_cond(host, 0, &ocr); if (!err) { if (mmc_attach_sd(host, ocr))

mmc_power_off(host); goto out; } 蓝色部分是遵照 SD 卡协议的 SD 卡启动过程,包括了非激活模式、卡识别模式和数据传输模式三种模

式共九种状态的转换,你需要参照相关规范来理解。可以先参考下面三章图对模式和状态,以及状态转换

有个初步了解。 我们 初的 SD 卡的状态时 inactive 状态调用 mmc_go_idle(host)后,发送命令 CMD0 是其处于 IDLE 状

态。 我们详细分析一下 mmc_go_idle memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = MMC_GO_IDLE_STATE; MMC_GO_IDLE_STATE 就是命令 CMD0 cmd.arg = 0;此命令无参数 cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC; err = mmc_wait_for_cmd(host, &cmd, 0);//见注 1 mmc_delay(1); 注 1:mmc_wait_for_cmd(host, &cmd, 0)是用来发送命令的,我们揭开它的神秘面纱吧。 memset(&mrq, 0, sizeof(struct mmc_request)); memset(cmd->resp, 0, sizeof(cmd->resp)); cmd->retries = retries; mrq.cmd = cmd;将命令嵌入到一个 mmc 请求中 cmd->data = NULL;mmc 命令的 data 部分设置为 NULL,这样表示我们要传输的是命令而不是数据 mmc_wait_for_req(host, &mrq);//关键部分 在该函数中调用了 mmc_start_request,而这个函数调用了 host->ops->request(host, mrq),这个 request

函数就是我们在前面分析的 s3cmci_request,这样 MMC 核心第二次核 HOST 层握手了 我们再看看: err = mmc_send_app_op_cond(host, 0, &ocr);//注一 if (!err) { if (mmc_attach_sd(host, ocr))//注二 mmc_power_off(host); goto out; 注一:实际上是要发送 ACMD41 命令,这条命令可以用来获取 SDcard 的允许电压范围值,由于这是

一条应用命令,所有发送它之前需要发送 CMD_55 命令。执行完后 card 状态变为 READY 获取的电压范围

保存在 ocr 中,再调用 mmc_attach_sd(host, ocr)看这个电压范围是否满足主机的要求,不满足,则 power_off主机。

注二:mmc_attach_sd 完成匹配,和初始化卡的功能 host->ocr = mmc_select_voltage(host, ocr);看是否匹配,如果匹配则做下面初始化工作

mmc_sd_init_card(host, host->ocr, NULL);我们分析该函数 (1)mmc_all_send_cid()这个函数发生 CMD2,获取卡的身份信息,进入到身份状态 (2)card = mmc_alloc_card(host, &sd_type);分配一张 SD 类型的 card 结构 (3)接着调用 mmc_send_relative_add,获取卡的相对地址,注意一前卡和主机通信都采用默认地址,现在

有了自己的地址了,进入到 stand_by 状态 (4)通过发送 SEND_CSD (CMD9) 获取 CSD 寄存器的信息,包括 block 长度,卡容量等信息 (5) mmc_select_card(card)发送 CMD7,选中目前 RADD 地址上的卡,任何时候总线上只有一张卡被选中,

进入了传输状态, (6)调用 mmc_app_send_scr 发送命令 ACMD51 获取 SRC 寄存器的内容,进入到 SENDING-DATA 状

态 在函数中还将获得的各个卡寄存器的内容解码,并保存到 cmd 结构的相应成员中。 (7)if (host->ops->get_ro(host) > 0) mmc_card_set_readonly(card); 通过调用 get_ro(host)函数,实际上就是 s3cmci_get_ro 函数了。我们判断是否写保护,如果是的,将 card

状态设置为只读状态 后再 mmc_attach_sd 里,我们将 card 结构添加进去

mmc_add_card(host->card); dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);这里我们以 host 名+rca 地

址来命名卡我们可以看到在/sys/devices/platform/s3c2440-sdi/mmc_host:mmc0/下出现 mmc0:0002 的目录,

这个 0002 就是 rca 地址 到这里我们分析完了 MMC 的核心层。

5. CARD 层分析

因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的 SD 卡如何实

现为块设备的。先看 block.C 中的 probe 函数 MMC 块设备用如下结构表示: struct mmc_blk_data { spinlock_t lock; struct gendisk *disk; struct mmc_queue queue; unsigned int usage; unsigned int read_only; }; 我们先看 mmc_blk_alloc( ) devidx = find_first_zero_bit(dev_use, MMC_NUM_MINORS); if (devidx >= MMC_NUM_MINORS)//这表明我们的 mmc 层对多支持 16 个 card,每个 card 占 8 分

return ERR_PTR(-ENOSPC); __set_bit(devidx, dev_use); md->disk = alloc_disk(1 << MMC_SHIFT);//分配一个磁盘,8 个分区 //8 partion if (md->disk == NULL) { ret = -ENOMEM; goto err_kfree; } spin_lock_init(&md->lock); md->usage = 1; ret = mmc_init_queue(&md->queue, card, &md->lock);//注一 if (ret) goto err_putdisk; md->queue.issue_fn = mmc_blk_issue_rq;//这个函数很重要,待会详细分析 md->queue.data = md; md->disk->major = MMC_BLOCK_MAJOR; md->disk->first_minor = devidx << MMC_SHIFT; md->disk->fops = &mmc_bdops;磁盘的操作函数 md->disk->private_data = md; md->disk->queue = md->queue.queue; md->disk->driverfs_dev = &card->dev; /* * As discussed on lkml, GENHD_FL_REMOVABLE should: * * - be set for removable media with permanent block devices * - be unset for removable block devices with permanent media * * Since MMC block devices clearly fall under the second * case, we do not set GENHD_FL_REMOVABLE. Userspace * should use the block device creation/destruction hotplug * messages to tell when the card is present. */这个注释如何理解呢? sprintf(md->disk->disk_name, "mmcblk%d", devidx);//这个名字将在/proc/device 下出现 我们可以看到在/sys/block 下有个"mmcblk0

blk_queue_hardsect_size(md->queue.queue, 512);//设置硬件扇区的容量 } 注一: mq->queue = blk_init_queue(mmc_request, lock);初始化将 request 函数与队列绑定 if (!mq->queue) return -ENOMEM; mq->queue->queuedata = mq; mq->req = NULL; blk_queue_prep_rq(mq->queue, mmc_prep_request); //命令预处理,为驱动程序在返回 evl_next_request 之前,提供检查和预处理请求的机制,详细见 LDD3

P485 //command prepare process blk_queue_ordered(mq->queue, QUEUE_ORDERED_DRAIN, NULL);// //barrier request 屏障请求,防止重新组合产生的错误,设置标准后,保证请求的数据及时写入到介

质。 mq->sg = kmalloc(sizeof(struct scatterlist) * host->max_phys_segs, GFP_KERNEL); if (!mq->sg) { ret = -ENOMEM; goto cleanup_queue; } sg_init_table(mq->sg, host->max_phys_segs); } //分配 scatterlist 结构体 mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd"); 后设置了一个内核线程,线程关联的函数

是 mmc_queue_thread,这个很重要,我们待会分析。 接下来调用 mmc_blk_set_blksize 来设置 block 的长度为 512。 一切都准备好了以后激活磁盘:add_disk(md->disk); 后来分析 request 函数:

* * Generic MMC request handler. This is called for any queue on a * particular host. When the host is not busy, we look for a request * on any queue on this host, and attempt to issue it. This may * not be the queue we were asked to process.也就是说,elv_next_request 返回来的 req 不一定是 mq->req */ static void mmc_request(struct request_queue *q) {

struct mmc_queue *mq = q->queuedata; struct request *req; int ret; if (!mq) { printk(KERN_ERR "MMC: killing requests for dead queue\n"); while ((req = elv_next_request(q)) != NULL) { do { ret = __blk_end_request(req, -EIO, blk_rq_cur_bytes(req));//没有可以处理的请求,则就素这个请求 } while (ret); } return; } if (!mq->req) wake_up_process(mq->thread);//注一 } 注一:我们发现,与 LDD3 中介绍的块设备编程方法不同,并没有出来任何与 bio 结构相关的东西,当

请求获取后,我们通过什么来进行数据块的传输呢,这里就是通过唤醒 mq->thread 线程来实现的,这个线

程实际上就是 mmc_queue_thread 函数 static int mmc_queue_thread(void *d) { struct mmc_queue *mq = d; struct request_queue *q = mq->queue; current->flags |= PF_MEMALLOC; down(&mq->thread_sem); do { struct request *req = NULL; spin_lock_irq(q->queue_lock); set_current_state(TASK_INTERRUPTIBLE); if (!blk_queue_plugged(q)) req = elv_next_request(q); mq->req = req; spin_unlock_irq(q->queue_lock); if (!req) { if (kthread_should_stop()) {

set_current_state(TASK_RUNNING); break; } up(&mq->thread_sem); schedule(); down(&mq->thread_sem); continue; } set_current_state(TASK_RUNNING); //蓝色部分不是很理解,大概的意思应该还是获取一个可处理的请求 mq->issue_fn(mq, req);//注一 } while (1); up(&mq->thread_sem); return 0; } 注一:我们看看 issue_fn 函数做了些什么,这个函数相当复杂 我们看关键的部分: brq.data.sg = mq->sg; brq.data.sg_len = mmc_queue_map_sg(mq); /* * Adjust the sg list so it is the same size as the * request. */ if (brq.data.blocks != req->nr_sectors) { int i, data_size = brq.data.blocks << 9; struct scatterlist *sg; for_each_sg(brq.data.sg, sg, brq.data.sg_len, i) { data_size -= sg->length; if (data_size <= 0) { sg->length += data_size; i++; break; } } brq.data.sg_len = i; }

以上这些代码用来准备 scatterlist,这是数据传输的缓冲区 mmc_wait_for_req(card->host, &brq.mrq);接下来我们向 host发送请求,这个函数应该很熟悉了,

它的 后一句就是 host->ops->request(host, mrq),这样就和我们驱动程序的 request 联系起来了,由于这次

cmd—>data 成员不再为空,所以启动的是数据传输了。

6. 实验

将默认的平台信息作了更改,这样 .get_ro = s3cmci_get_ro, .get_cd = s3cmci_card_present, 两个函数就有实际的作用了 static struct s3c24xx_mci_pdata s3cmci_def_pdata = { /* This is currently here to avoid a number of if (host->pdata) * checks. Any zero fields to ensure reaonable defaults are picked. */ .detect_invert=0, .wprotect_invert=1, .gpio_detect=1, .gpio_wprotect = 1 , }; 不过还有一点不清楚的是, host_dodma 设置为 1 的时候,在/sdcard 下找不到任何东西 /proc/devices 中也查找不到相应的设备

从打印的信息看: 7>mmc0: clock 0Hz busmode 1 powermode 1 cs 0 Vdd 21 width 0 timing 0 <6>s3c2440-sdi s3c2440-sdi: running at 0kHz (requested: 0kHz). <7>mmc0: clock 197753Hz busmode 1 powermode 2 cs 0 Vdd 21 width 0 timing 0 <6>s3c2440-sdi s3c2440-sdi: running at 198kHz (requested: 197kHz). <7>mmc0: clock 197753Hz busmode 1 powermode 2 cs 1 Vdd 21 width 0 timing 0 <6>s3c2440-sdi s3c2440-sdi: running at 198kHz (requested: 197kHz). <7>mmc0: starting CMD0 arg 00000000 flags 000000c0 <7>s3c2440-sdi s3c2440-sdi: CMD[OK] #1 op:0 arg:0x00000000 flags:0x08c0 retries:0 R0:0x00000000 <7>mmc0: req done (CMD0): 0: 00000000 00000000 00000000 00000000 发送命令基本都是成功的,为什么会这样???

7 结论

到此为止,按照数据和命令流的方向,我们分析了 MMC 子系统的基本结构,很多细节的地方还不是

很清楚,不过至少为写驱动程序做了相应的准备了。

3.18 移植 UDA1341 音频驱动

3.18.1 在初始化文件中加入 UDA1341 设备结构

Linux-2.6.32.2 已 经 完 美 的 支 持 UDA1341 音 频 芯 片 的 驱 动 , 我 们 只 要 在

arch/arm/mach-s3c2440/mach-mini2440.c 文件中注册 UDA1341 平台设备的控制端口就可以了,

打开 mach-mini2440.c,添加如下内容: ;在文件首部添加头文件 #include <sound/s3c24xx_uda134x.h> ;在 LCD 平台设备后面添加 UDA1341 设备结构 static struct s3c24xx_uda134x_platform_data s3c24xx_uda134x_data = { .l3_clk = S3C2410_GPB(4), .l3_data = S3C2410_GPB(3), .l3_mode = S3C2410_GPB(2), .model = UDA134X_UDA1341, }; static struct platform_device s3c24xx_uda134x = { .name = "s3c24xx_uda134x", .dev = { .platform_data = &s3c24xx_uda134x_data, } }; ;注册 UDA1341 设备平台到内核中 static struct platform_device *mini2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_rtc, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &mini2440_device_eth, &s3c24xx_uda134x, &s3c_device_nand, };

这样,我们基本就添加好了 UDA1341 音频设备的驱动,接下来我们在内核中配置该

驱动。

3.18.2 在内核中配置 UDA1341 设备驱动

在内核源代码目录输入:make menuconfig 开始配置内核,依次选择如下子菜单,找

到音频驱动配置菜单: Device Drivers ---> <*> Sound card support --->

如图,按空格选中“[*] Preclaim OSS device numbers ”,再选中“<*> Advanced Linux Sound Architecture --->”,并回车进入该子菜单

出现音频驱动体系结构哦诶只菜单,在这里,我们选择 OSS 接口相关的配置选项,需

要注意的是,这里的 OSS 接口其实是基于 ALSA 接口创建的,因为新的内核中现在都已经改

用 ALSA 设计,这里是为了和以前的软件兼容才这样做的,如图

再选择“<*> ALSA for SoC audio support --->”子菜单并回车进入,如图

在这里,我们可以看到专门为 S3C24xx 系列芯片(包括 S3C2410/2440/2443 等)而做的

配置选项,打开 linux-2.6.32.2/sound/soc/s3c24xx/Makefile 文件就可以看到,如图

我们的开发板使用的是 UDA1341 音频芯片,在这里当然要选择“-*- SoC I2S Audio

support UDA134X wired to a S3C24XX”了。 退出保存以上各项配置。

3.18.3 mp3 放音测试

在内核源代码目录执行:make zImage,把生成的内核映像文件烧写到开发板,依然使

用友善之臂提供的文件系统 root_qtopia,系统启动后,使用系统自带的 madplay 软件播放一首

mp3 进行测试,把音箱或者耳机插入开发板的绿色音频输出插座,就可以听到悦耳的音乐了,

如图:

3.18.4 修正驱动中的录音代码

虽然播放 mp3 很正常,但当使用系统自带的录音程序进行录音时,我们发现无法听到

任何结果,这是因为开发板的录音电路和 SMDK2440 目标板的电路是有所区别的。 Mini2440 开发板的录音电路如下图所示:

可见,mini2440 开发板使用的录音通道为 VIN2,而 SMDK2440 使用的则是 VIN1,打

开 linux-2.6.32.2/sound/soc/codecs/ uda134x.c,在大概 201 行添加如下红色代码: uda134x->slave_substream = substream; } else uda134x->master_substream = substream; uda134x_write(codec, 2, 2|(5U<<2)); //把录音通道改为 VIN2 return 0; } static void uda134x_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) {

这样,我们就完成了录音驱动的修正,在内核源代码目录下执行:make zImage,重新

编译内核并烧写到开发板中。

3.18.5 录音测试

打开 Qtopia 中的“录音机”测试程序,如图

根据提示,点“REC”按钮开始录音,这时对着板上的麦克风说话,可以看到录音的

波形,点“STOP”按钮结束录音,如图:

此时可以点“PLAY”按钮播放刚才的录音,同时录制的音频文件将以“WAV”格式

自动存储到“文档”中:

说明:Qtopia 2.2.0 系统自带了一个录音程序,中文名为“语音便签”,但它不能正常

使用板上的麦克风进行录制,为了保持代码的原始性,我们没有对此做任何修改。

3.19 修整串口驱动

3.19.1 把 UART2 改为普通串口驱动

S3C2440 芯片具有 3 个串口:UART0,1,2,我们下载的 Linux-2.6.32.2 已经具备完善的

UART0,1 的驱动,但对 UART2 却用作了红外通讯(Irda),因此我们需要把 UART2 驱动稍微调

整一下,以便作为普通串口来用。 先看看 S3C2440 串口部分寄存器的说明,如下图

接下来我们修改内核中关于 UART2 的配置,打开 mach-mini2440.c 文件,找到,如下

红色代码为修改后的: static struct s3c2410_uartcfg mini2440_uartcfgs[] __initdata = { [0] = { .hwport = 0, .flags = 0, .ucon = 0x3c5, .ulcon = 0x03, .ufcon = 0x51, }, [1] = { .hwport = 1, .flags = 0, .ucon = 0x3c5, .ulcon = 0x03, .ufcon = 0x51, },

/* 把 UART2 改为普通串口 */ [2] = { .hwport = 2, .flags = 0, .ucon = 0x3c5, .ulcon = 0x03, .ufcon = 0x51, } };

再修改串口所使用的端口初始化,打开 linux-2.6.32.2/drivers/serial/samsung.c,在大

概 432 行左右,添加如下红色部分代码: //需要添加的头文件 #include <linux/gpio.h> #include <mach/regs-gpio.h>

ourport->tx_claimed = 1; dbg("s3c24xx_serial_startup ok\n"); /* the port reset code should have done the correct * register setup for the port controls */

//串口 2 对应的端口初始化 if (port->line == 2) { s3c2410_gpio_cfgpin(S3C2410_GPH(6), S3C2410_GPH6_TXD2); s3c2410_gpio_pullup(S3C2410_GPH(6), 1); s3c2410_gpio_cfgpin(S3C2410_GPH(7), S3C2410_GPH7_RXD2); s3c2410_gpio_pullup(S3C2410_GPH(7), 1); } return ret;

这样,我们就完成了 UART2 的修整。

3.19.2 测试串口

回到内核源代码目录,执行:make zImage,把重新生成的内核烧写到开发板中,依

然使用友善之臂提供的 root_qtopia 文件系统,因为里面包含了一个图形界面的串口助手测试

程序,极其方便测试。 需要注意的是,mini2440 开发板并没有把 UART2 做成 RS232 端口引出,而是直接把

它通过 CON3 排针引出了,因此需要一条 RS232 转接板(该转接板由友善之臂提供),如图:

把它连接到 mini2440 开发板的 CON3 接口,如图

并把引出的串口通过直连线连接到另一台电脑的串口上,连接好开发板的电源,并设

置 S2 为 nand 启动,打开电源,进入 Qtopia 系统,在“友善之臂”程序组中点“串口助手”

图标,打开相应的程序界面,如图:

从该程序窗口的标题可以看到,默认设置为“ttySAC1 115200 8N1 [C]”,它表示默认

端口的设置: - 串口设备:/dev/ttySAC1,它对应 CPU 的第二个串口 UART1 - 波特率:115200 - 数据位:8 - 流控制:无 - 停止位:1 - [C]:表示字符模式,如果是[H]则表示 16 进制模式 上图中有两个“编辑框”区域,上面的“编辑框”是用于显示接收到的数据,它实际

上是不能编辑的;下面的“编辑框”可以通过 USB 键盘或者 Qtopia 的软键盘获取输入。 因为我们要测试 UART2,因此点“Setting”按钮,在设置窗口中选中/dev/ttySAC2,

然后返回主界面,点 Connect 按钮,以打开开发板串口/dev/ttySAC2,在窗口下面的编辑框输

入一些字符,点 Send 按钮,就可以向与它相连的串口设备发送数据了,下图显示的是通过

Windows 超级终端接收的数据截图(注意:与此终端对应的串口也应该设置为 115200 8N1)。

更详细的说明可以参考用户手册的相关说明,用户也可以自行多试试,毕竟串口助手

比较界面比较简单,很容易看懂。

3.20 移植 I2C-EEPROM 驱动

3.20.1 在内核中配置 I2C 驱动

Linux-2.6.32.2 对 S2C2440 的 I2C 接口提供了完善的驱动,因此我们只需在内核中配置

一下即可使用。 提示:其实 Linux-2.6.32.2 内核缺省的 mini2440_defconfig 就已经配置好了 I2C 驱动,

我们只不过在此打开看看具体的配置路径。 在内核源代码目录执行:make menuconfig,进入内核配置主菜单,依次选择进入如下

子菜单: Device Drivers ---> <*> I2C support ---> I2C Hardware Bus support --->

如图,我们看到这里已经选择好了“<*> S3C2410 I2C Driver”,这里的 S3C2410 也可

以适用于 S3C2440,因为它们的 I2C 端口及寄存器定义都是完全相同的。

以上配置所对应的驱动源代码为:linux-2.6.32.2/drivers/i2c/busses/i2c-s3c2410.c,感兴

趣的用户可以自己打开看看。

3.20.2 测试 I2C-EEPROM

测试程序名称: i2c 备注

测试程序源代码文件名 Eeprom.c 24cXX.c

测试程序源代码位置 解压 linux\examples.tgz 可得

交叉编译器 Arm-linux-gcc-4.3.2 with EABI

开发板上对应的设备名 /dev/i2c/0

对应的内核驱动源代码 Linux-src/drivers/i2c/busses/i2c-s3c2440.c

其他:

Mini2440 为了方便用户测试,基于 I2C 总线挂接了一个 EEPROM 芯片,它是 AT24C08,通过写入和读取该芯片,我们就可以测试 I2C 总线驱动了。

在内核根目录下执行:make zImage,把生成的新内核烧写到开发板中,依然使用友

善之臂提供的root_qtopoia,因为里面已经包含了I2C-EEPROM测试程序,分别有命令行和图形

界面的,其中命令行测试程序名为“i2c”,它是基于开源的eeprog软件修改而来的,eeprog软件的下载地址是:http://codesink.org/eeprog.html

在命令行种输入:i2c –w 可以向板子的 24C08 器件中写入数据(0x00-0xff)

在命令行中输入:i2c –r 可以从板子的 24C08 器件中读出输出

3.21 移植看门狗驱动

3.21.1 在内核中配置看门狗驱动

Linux-2.6.32.2 内核具有完善的 S3C2440 的看梦狗驱动,我们只需配置一下就可以使用

了。 提示:其实 Linux-2.6.32.2 内核缺省的 mini2440_defconfig 就已经配置好了看门狗驱动,

我们只不过在此打开看看具体的配置路径。 在内核源代码目录执行:make menuconfig,进入内核配置主菜单,依次选择进入如

下子菜单: Device Drivers ---> [*] Watchdog Timer Support --->

如图,打开看门狗配置菜单,在这里可以选择对 S2C2410/2440 中看门狗的配置选项。

以上配置所对应的驱动源代码为:linux-2.6.32.2/drivers/watchdog/s3c2410_wdt.c,感

兴趣的用户可以自己打开看看。

Administrator
铅笔

3.21.2 关于打开和关闭看门狗

在看门狗驱动程序中,我们注意到有这样一个函数,注意其中的红色部分字体: #define PFX "s3c2410-wdt: " #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) //这里表明看门狗的默认时间是 15 秒,如果超过此时间系统将自行重启 #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { /* * Refresh the timer. */ if (len) { if (!nowayout) { size_t i; /* In case it was set long ago */ expect_close = 0; for (i = 0; i != len; i++) { char c; if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 42; } } s3c2410wdt_keepalive(); } return len; }

根据此代码,我们判定可以在打开看门狗设备(/dev/watchdog)之后不断的向看门狗随

便写入写入一些数据以实现喂狗操作,但是,当写入“V“时,就可以关闭看门狗了。

3.21.3 测试看门狗

虽然友善之臂提供了一个漂亮的图形界面的看门狗测试程序,但因为操作看门狗比较

简单,我们不需要编写任何代码即可测试。 根据上面的分析,我们可以使用 echo 命令向/dev/watchdog 设备随便写入一些数据即

可开启看门狗,比如:echo 0 > /dev/watchdog,如图:

此时,如果静等 15 秒钟,系统将会自动重启,这样就证实了看门狗已经被开启了。 如果 15 秒之内,我们不停地重复“喂狗”操作,也就是不停的使用 echo 命令向看门

狗写入数据,那么系统就不会重启。 那么,如何停止看门狗呢?根据上面的分析,只要写入“V”就可以了。需要知道的

是,但我们使用 echo 命令向/dev/watchdog 写入数据的时候,同时也把“回车”给送进去了,

因此可以这样操作:echo –n V >/dev/watchdog 这里的“-n”意思是“去掉回车”,为了测试,你可以先输入: echo 0 > /dev/watchdog 接着再输入: echo –n V > /dev/watchdog 然后接着静等,过了好久,系统依然在正常运行,这就证明了看门狗已经被关闭了。

Administrator
铅笔

3.22 最简单的 LED 驱动

3.22.1 LED 驱动原理及编写

在前面的章节,我们为内核添加过 LCD 背光驱动、触摸屏驱动、ADC 驱动等,已经

对嵌入式 Linux 的驱动有所了解,我们在此介绍的 LED 驱动实际上是一个十分典型的字符设

备驱动程序,它就像学习 C 语言时的“Hello,World”程序一样,代表了你对嵌入式 Linux 驱动

程序的入门认识,因此我们将对此驱动进行详细的介绍和分析,以便深入掌握编写驱动程序的

方法和原理。 说明:在 mini2440 的用户手册中,我们其实也已经介绍了 LED 驱动,下面的内容也

是基于用户手册而来的。 要写实际的驱动,就必须了解相关的硬件资源,比如用到的寄存器,物理地址,中断

等,在 mini2440 的原理图中,我们可以看到 LED 的硬件连接是这样的:

查看 S3C2440 数据手册,可以知道,他们对应的硬件资源如下表

LED 序号 对应的 GPIO 对应的 CPU 引脚 LED1 GPB5 K2 LED2 GPB6 L5 LED3 GPB7 K7 LED4 GPB8 K5

GPIO 是通用输入输出口的英文简称,在 S3C2440 芯片中,很多的端口都是可复用的,

GPIO 只是其中的一个功能,比如这里的 GPB5 端口,既可以用作普通的 GPIO,也可以用作

专用功能 nXBACK,因此我们需要设置相应的端口寄存器,以改变引脚的用途。 在这里,四个 LED 是采用 GPBCON 寄存器上的 4 组 2bit 位来配置对应引脚的用途。4

组 2bit 位的功能都一样:00 表示输入,01 表示输出,10 为特殊功能,11 是保留的。见下面的截图:

在 mini2440 开发板中, LED1 对应的是 GPB5, GPB5 使用[11:10]位 LED2 对应的是 GPB6, GPB6 使用[12:13]位 LED3 对应的是 GPB7, GPB7 使用[14:15]位 LED4 对应的是 GPB8, GPB8 使用[16:17]位 因此,驱动程序中需要先设置 LED 为输出状态,也就是要把对应的 GPBX 设置为 01。

而 GPBDAT 寄存器用来对应 4 个 LED 的数值状态,GPBDAT5 就对应 GPB5,GPBDAT6就对应 GPB6,以此类推。根据原理图可以看出,当 GPIO 输出为低电平时有效,就是说当

GPBDAT 寄存器位置为 0 时,GPB5,6,7,8 将输出低电平,对应的 LED 就发光。

在软件中,要操作所用到的 IO 口,我们通常调用一些现成的函数或者宏,例如:

s3c2410_gpio_cfgpin,为什么是 S3C2410 的呢?因为三星出品的 S3C2440 芯片所用的寄存器

名称以及资源分配大部分和 S3C2410 是相同的,在目前各个版本的 Linux 系统中,也大都采

用了相同的函数定义和宏定义。 它们从哪里定义?细心的用户或许很快就想到它们和体系结构有关,因此你可以在

linux-2.6.32.2/arch/arm/plat-s3c24x/gpio.c 文件中找到该函数的定义和实现,由于内核版本的

变更,该函数的定义及实现有可能也会变动,但在此我们只要大概了解就可以了,因为该函数

的名称一般是不会变的,我们可以在其他驱动源代码中找到包含该函数的包含头文件,然后依

此照搬就可以了,在 gpio.c 文件中,s3c2410_gpio_cfgpin 函数的实现如下所示: void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function) {

Administrator
铅笔

void __iomem *base = S3C24XX_GPIO_BASE(pin); unsigned long mask; unsigned long con; unsigned long flags; if (pin < S3C2410_GPIO_BANKB) { mask = 1 << S3C2410_GPIO_OFFSET(pin); } else { mask = 3 << S3C2410_GPIO_OFFSET(pin)*2; } switch (function) { case S3C2410_GPIO_LEAVE: mask = 0; function = 0; break; case S3C2410_GPIO_INPUT: case S3C2410_GPIO_OUTPUT: case S3C2410_GPIO_SFN2: case S3C2410_GPIO_SFN3: if (pin < S3C2410_GPIO_BANKB) { function -= 1; function &= 1; function <<= S3C2410_GPIO_OFFSET(pin); } else { function &= 3; function <<= S3C2410_GPIO_OFFSET(pin)*2; } } /* modify the specified register wwith IRQs off */ local_irq_save(flags); con = __raw_readl(base + 0x00); con &= ~mask; con |= function;

__raw_writel(con, base + 0x00); local_irq_restore(flags); }

实际上,我们并不需要关心这些,写驱动时只要会使用他们就可以了,除非你所使用

的 CPU 体系平台尚没有被 Linux 所支持,因为大部分常见的嵌入式平台都已经有了很完善的

类似定义,你不需要自己去编写。 在下面的驱动程序清单中,你可以看到 s3c2410_gpio_cfgpin 被调用的情况。除此之外,

你还需要调用一些和设备驱动密切相关的基本函数,如注册设备 misc_register,填写驱动函数

结构 file_operations,以及像 Hello,Module(见用户手册的例子 )中那样的 module_init 和

module_exit 函数等。 有些函数并不是必须的,随着你对 Linux 驱动开发的进一步了解和阅读更多的代码,

你自然明白。在 drivers/char 目录下,我们建立一个驱动程序文件 mini2440_leds.c,内容如下: #include <linux/miscdevice.h> #include <linux/delay.h> #include <asm/irq.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/delay.h> #include <linux/moduleparam.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/string.h> #include <linux/list.h> #include <linux/pci.h> #include <linux/gpio.h> #include <asm/uaccess.h> #include <asm/atomic.h> #include <asm/unistd.h>

#define DEVICE_NAME "leds" //设备名(/dev/leds) //LED 对应的 GPIO 端口列表 static unsigned long led_table [] = { S3C2410_GPB(5), S3C2410_GPB(6), S3C2410_GPB(7), S3C2410_GPB(8), }; //LED 对应端口将要输出的状态列表 static unsigned int led_cfg_table [] = { S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, }; /*ioctl 函数的实现 * 在应用/用户层将通过 ioctl 函数向内核传递参数,以控制 LED 的输出状态 */ static int sbc2440_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { switch(cmd) { case 0: case 1: if (arg > 4) { return -EINVAL; } //根据应用/用户层传递来的参数(取反),通过 s3c2410_gpio_setpin 函数设置 LED 对应的端口寄存

器, s3c2410_gpio_setpin(led_table[arg], !cmd);

return 0; default: return -EINVAL;

} } /* * 设备函数操作集,在此只有 ioctl 函数,通常还有 read, write, open, close 等,因为本 LED 驱动在下面已经

* 注册为 misc 设备,因此也可以不用 open/close */ static struct file_operations dev_fops = { .owner = THIS_MODULE, .ioctl = sbc2440_leds_ioctl, }; /* * 把 LED 驱动注册为 MISC 设备 */ static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, //动态设备号 .name = DEVICE_NAME, .fops = &dev_fops, }; /* * 设备初始化 */ static int __init dev_init(void) { int ret; int i; for (i = 0; i < 4; i++) { //设置 LED 对应的端口寄存器为输出(OUTPUT) s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]); //设置 LED 对应的端口寄存器为低电平输出,在模块加载结束后,四个 LED 应该是全部都是发光

状态 s3c2410_gpio_setpin(led_table[i], 0); } ret = misc_register(&misc); //注册设备

printk (DEVICE_NAME"\tinitialized\n"); //打印初始化信息 return ret; } static void __exit dev_exit(void) { misc_deregister(&misc); } module_init(dev_init); //模块初始化,仅当使用 insmod/podprobe 命令加载时有用,如果设备不是通过模块方

式加载,此处将不会被调用 module_exit(dev_exit);//卸载模块,当该设备通过模块方式加载后,可以通过 rmmod 命令卸载,将调用此函

数 MODULE_LICENSE("GPL"); //版权信息 MODULE_AUTHOR("FriendlyARM Inc."); //开发者信息

说明:杂项设备(misc device) 杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在 Linux 内核的

include/linux 目录下有 Miscdevice.h 文件,要把自己定义的 misc device 从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号 10,一起归于 misc device,其实 misc_register 就是用主标号 10 调用 register_chrdev()的。也就是说,misc设备其实也就是特殊的字符设备

接下来,我们添加 LED 设备的内核配置选项,打开 drivers/char/Kconfig 文件,添加如

下红色部分内容: config DEVKMEM bool "/dev/kmem virtual device support" default y help Say Y here if you want to support the /dev/kmem device. The /dev/kmem device is rarely used, but can be used for certain kind of kernel debugging operations. When in doubt, say "N". config LEDS_MINI2440 tristate "LED Support for Mini2440 GPIO LEDs" depends on MACH_MINI2440 default y if MACH_MINI2440

Administrator
铅笔
Administrator
在文本上注释
字符设备是指在I/O传输过程中以字符为单位进行传输的设备,例如键盘,打印机等。请注意,以字符为单位并不一定意味着是以字节为单位,因为有的编码规则规定,1个字符占16比特,合2个字节。

help This option enables support for LEDs connected to GPIO lines on Mini2440 boards. config MINI2440_ADC bool "ADC driver for FriendlyARM Mini2440 development boards" depends on MACH_MINI2440 default y if MACH_MINI2440 help this is ADC driver for FriendlyARM Mini2440 development boards Notes: the touch-screen-driver required this option

接下来,再根据该驱动的配置定义,把对应的驱动目标文件加入内核中,打开

linux-2.6.32.2/drivers/char/Makefile 文件,添加如下红色部分内容: obj-$(CONFIG_JS_RTC) += js-rtc.o js-rtc-y = rtc.o obj-$(CONFIG_LEDS_MINI2440) += mini2440_leds.o obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o # Files generated that shall be removed upon make clean clean-files := consolemap_deftbl.c defkeymap.c

这样,我们就在内核中添加做好了 LED 驱动

3.22.2 配置编译新内核并测试 LED

接上面的步骤,在内核源代码目录下执行:make menuconfig 重新配置内核,依次选

择进入如下子菜单项: Device Drivers ---> Character devices --->

如题,进入 LED 驱动配置菜单:

在此按空格键选择“<*> LED Support for Mini2440 GPIO LEDs (NEW)”选项,并退出

保存内核配置。 在内核源代码根目录下执行;make zImage,把生成的新内核烧写到开发板中。

3.22.3 测试 LED

为了测试该驱动程序,我们还需要编写一个简单的测试程序,用来调用驱动程序中的

ioctl 函数,以达到通过应用程序控制 LED 的目的,新建一个 leds-test.c 文件,内容如下: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> int main(int argc, char **argv) { int on; int led_no; int fd; if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 || on < 0 || on > 1 || led_no < 0 || led_no > 3) {

fprintf(stderr, "Usage: leds led_no 0|1\n"); exit(1); } fd = open("/dev/leds0", 0); if (fd < 0) { fd = open("/dev/leds", 0); } if (fd < 0) { perror("open device leds"); exit(1); } ioctl(fd, on, led_no); close(fd); return 0; }

实际上,我们在光盘中已经提供了该测试程序的源代码,它位于\linux 示例代码

\examples\leds 目录中,文件名为 led.c。 在命令行下执行: #arm-linux-gcc –o leds-test leds-test.c 将生成可执行目标文件 leds-test,通过网络 ftp 或者优盘把它复制到开发板中,比如放

在/home/plg 目录下(注意:开发板缺省的文件系统已经有了 led 测试程序,所以我们在此改名

为 leds-test),在开发板的命令行终端执行: #/etc/rc.d/init.d/leds stop 该命令将停止 led-player 对 led 的控制,关于 led-player,可以在用户手册中查看详细

的说明。使用 leds-test 控制 led 方法为: #leds-test 3 0 ;关闭 LED3 #leds-test 3 1 ;打开 LED3 其中第一个参数为要控制的 LED 序号,第二个参数代表关闭(0)或者打开(1)对应的

LED。如图

Administrator
铅笔
Administrator
铅笔
Administrator
铅笔

3.23 基于中断的按键驱动程序

3.23.1 硬件原理

Mini2440 具有 6 个用户测试按键,它们都是连接到 CPU 的中断引脚。如图:

从图中可以看出,6 个用户按键分别对应如下 CPU 资源引脚:

按键 对应的端口寄存器 对应的中断 对应的复用功能 K1 GPG0 EINT8 仅有 GPIO 和中断功能

K2 GPG3 EINT11 nSS1 K3 GPG5 EINT13 SPIMISO K4 GPG6 EINT14 SPIMOSI K5 GPG7 EINT15 SPICLK K6 GPG11 EINT19 TCLK

我们为何如此安排这些按键资源呢? 首先,它们都具备中断功能,因此可以直接做一些中断相关的实验,其次 GPG3,5,6,7

这一组合可以形成一个全功能的 SPI 接口,我们知道,有些全键盘就是通过 SPI 接口扩展实现

的,比如三星的公板 SMDK2440 就带有这种接口的键盘,只不过它需要添加一个 SPI 接口的

键盘芯片来实现。所以,我们不但在开发板上直接把这些引脚接到按键上,而且还特意增加了

CON12 座以方便把这些按键引出到面板使用,或者作为扩展全功能键盘的接口。这也是

mini2440 精心设计的细节之一。

3.23.2 驱动程序分析及编写

在/linux-2.6.32.2/drivers/char/目录下创建一个新的驱动程序文件 mini2440_buttons.c,内容及详细注释如下: #include <linux/module.h>

Administrator
铅笔

#include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/irq.h> #include <asm/irq.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #include <linux/sched.h> #include <linux/gpio.h> #define DEVICE_NAME "buttons" //设备名称 /*定义中断所用的结构体*/ struct button_irq_desc { int irq; //按键对应的中断号 int pin; //按键所对应的 GPIO 端口 int pin_setting; //按键对应的引脚描述,实际并未用到,保留 int number; //定义键值,以传递给应用层/用户态 char *name; //每个按键的名称 }; /*结构体实体定义*/ static struct button_irq_desc button_irqs [] = { {IRQ_EINT8 , S3C2410_GPG(0) , S3C2410_GPG0_EINT8 , 0, "KEY0"}, {IRQ_EINT11, S3C2410_GPG(3) , S3C2410_GPG3_EINT11 , 1, "KEY1"}, {IRQ_EINT13, S3C2410_GPG(5) , S3C2410_GPG5_EINT13 , 2, "KEY2"}, {IRQ_EINT14, S3C2410_GPG(6) , S3C2410_GPG6_EINT14 , 3, "KEY3"}, {IRQ_EINT15, S3C2410_GPG(7) , S3C2410_GPG7_EINT15 , 4, "KEY4"}, {IRQ_EINT19, S3C2410_GPG(11), S3C2410_GPG11_EINT19, 5, "KEY5"}, }; /*开发板上按键的状态变量,注意这里是’0’,对应的 ASCII 码为 30*/

static volatile char key_values [] = {'0', '0', '0', '0', '0', '0'}; /*因为本驱动是基于中断方式的,在此创建一个等待队列,以配合中断函数使用;当有按键按下并读取到键

值时,将会唤醒此队列,并设置中断标志,以便能通过 read 函数判断和读取键值传递到用户态;当没有按

键按下时,系统并不会轮询按键状态,以节省时钟资源*/ static DECLARE_WAIT_QUEUE_HEAD(button_waitq); /*中断标识变量,配合上面的队列使用,中断服务程序会把它设置为 1,read 函数会把它清零*/ static volatile int ev_press = 0; /*本按键驱动的中断服务程序*/ static irqreturn_t buttons_interrupt(int irq, void *dev_id) { struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id; int down;

// udelay(0); /*获取被按下的按键状态*/

down = !s3c2410_gpio_getpin(button_irqs->pin); /*状态改变,按键被按下,从这句可以看出,当按键没有被按下的时候,寄存器的值为 1(上拉),但按

键被按下的时候,寄存器对应的值为 0*/ if (down != (key_values[button_irqs->number] & 1)) { // Changed /*如果 key1 被按下,则 key_value[0]就变为’1’,对应的 ASCII 码为 31*/ key_values[button_irqs->number] = '0' + down; ev_press = 1; /*设置中断标志为 1*/ wake_up_interruptible(&button_waitq); /*唤醒等待队列*/ } return IRQ_RETVAL(IRQ_HANDLED); } /* *在应用程序执行 open(“/dev/buttons”,…)时会调用到此函数,在这里,它的作用主要是注册 6 个按键的中断。

*所用的中断类型是 IRQ_TYPE_EDGE_BOTH,也就是双沿触发,在上升沿和下降沿均会产生中断,这样做

是为了更加有效地判断按键状态 */

Administrator
铅笔
Administrator
铅笔

static int s3c24xx_buttons_open(struct inode *inode, struct file *file) { int i; int err = 0; for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) { continue; } /*注册中断函数*/ err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_BOTH, button_irqs[i].name, (void *)&button_irqs[i]); if (err) break; }

if (err) { /*如果出错,释放已经注册的中断,并返回*/

i--; for (; i >= 0; i--) { if (button_irqs[i].irq < 0) { continue; } disable_irq(button_irqs[i].irq); free_irq(button_irqs[i].irq, (void *)&button_irqs[i]); } return -EBUSY; } /*注册成功,则中断队列标记为 1,表示可以通过 read 读取*/ ev_press = 1;

/*正常返回*/ return 0; } /* *此函数对应应用程序的系统调用 close(fd)函数,在此,它的主要作用是当关闭设备时释放 6 个按键的中断*处理函数

Administrator
铅笔
Administrator
铅笔

*/ static int s3c24xx_buttons_close(struct inode *inode, struct file *file) { int i; for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) { continue; } /*释放中断号,并注销中断处理函数*/ free_irq(button_irqs[i].irq, (void *)&button_irqs[i]); } return 0; } /* *对应应用程序的 read(fd,…)函数,主要用来向用户空间传递键值 */ static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { unsigned long err; if (!ev_press) { if (filp->f_flags & O_NONBLOCK) /*当中断标识为 0 时,并且该设备是以非阻塞方式打开时,返回*/ return -EAGAIN; else /*当中断标识为 0 时,并且该设备是以阻塞方式打开时,进入休眠状态,等待被唤醒*/ wait_event_interruptible(button_waitq, ev_press); } /*把中断标识清零*/ ev_press = 0; /*一组键值被传递到用户空间*/ err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));

return err ? -EFAULT : min(sizeof(key_values), count); } static unsigned int s3c24xx_buttons_poll( struct file *file, struct poll_table_struct *wait) {

unsigned int mask = 0; /*把调用 poll 或者 select 的进程挂入队列,以便被驱动程序唤醒*/

poll_wait(file, &button_waitq, wait); if (ev_press) mask |= POLLIN | POLLRDNORM; return mask; } /*设备操作集*/ static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = s3c24xx_buttons_open, .release = s3c24xx_buttons_close, .read = s3c24xx_buttons_read, .poll = s3c24xx_buttons_poll, }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; /*设备初始化,主要是注册设备*/ static int __init dev_init(void) { int ret; /*把按键设备注册为 misc 设备,其设备号是自动分配的*/ ret = misc_register(&misc); printk (DEVICE_NAME"\tinitialized\n"); return ret;

} /*注销设备*/ static void __exit dev_exit(void) { misc_deregister(&misc); } module_init(dev_init); //模块初始化,仅当使用 insmod/podprobe 命令加载时有用,如果设备不是通过模块方

式加载,此处将不会被调用 module_exit(dev_exit); //卸载模块,当该设备通过模块方式加载后,可以通过 rmmod 命令卸载,将调用此函

数 MODULE_LICENSE("GPL");//版权信息 MODULE_AUTHOR("FriendlyARM Inc.");//作者名字

3.23.3 把按键驱动加入内核

接下来,我们把按键驱动加入到内核中,打开 linux-2.6.32.2_fa/drivers/char/Kconfig 文

件,加入如下红色部分内容: config MINI2440_BUTTONS tristate "Buttons driver for FriendlyARM Mini2440 development boards" depends on MACH_MINI2440 default y if MACH_MINI2440 help this is buttons driver for FriendlyARM Mini2440 development boards config MINI2440_BUZZER tristate "Buzzer driver for FriendlyARM Mini2440 development boards" depends on MACH_MINI2440 default y if MACH_MINI2440 help this is buzzer driver for FriendlyARM Mini2440 development boards config MINI2440_ADC bool "ADC driver for FriendlyARM Mini2440 development boards" depends on MACH_MINI2440 default y if MACH_MINI2440 help

this is ADC driver for FriendlyARM Mini2440 development boards Notes: the touch-screen-driver required this option config BFIN_JTAG_COMM tristate "Blackfin JTAG Communication"

接下来,再根据该驱动的配置定义,把对应的驱动目标文件加入内核中,打开

linux-2.6.32.2/drivers/char/Makefile 文件,添加如下红色部分内容: obj-$(CONFIG_JS_RTC) += js-rtc.o js-rtc-y = rtc.o obj-$(CONFIG_LEDS_MINI2440) += mini2440_leds.o obj-$(CONFIG_MINI2440_BUTTONS) += mini2440_buttons.o obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o # Files generated that shall be removed upon make clean clean-files := consolemap_deftbl.c defkeymap.c

这样,我们就在内核中添加做好了 LED 驱动

3.23.4 配置编译新内核

接上面的步骤,在内核源代码目录下执行:make menuconfig 重新配置内核,依次选

择进入如下子菜单项: Device Drivers ---> Character devices --->

如题,进入按键驱动配置菜单:

Administrator
铅笔
Administrator
铅笔

在此按空格键选择“<*> Buttons driver for FriendlyARM Mini2440 development boards

(NEW) ”选项,并退出保存内核配置。 在内核源代码根目录下执行;make zImage,把生成的新内核烧写到开发板中。

3.23.5 测试按键

为了测试上面的按键驱动,创建一个按键测试程序:btn-test.c,内容如下: 提示:光盘中已经提供了按键测试的源代码程序,它位于 \linux 示例代码

\examples\buttons 目录中,源代码名为:buttons_test.c,在此重新命名为 btn-test.c 以作区分。 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/select.h> #include <sys/time.h> #include <errno.h> int main(void)

{ int buttons_fd; char buttons[6] = {'0', '0', '0', '0', '0', '0'}; //定义按键值变量,对于驱动函数中的 key_values 数组 /*打开按键设备/dev/buttons*/ buttons_fd = open("/dev/buttons", 0); if (buttons_fd < 0) { /*打开失败则退出*/ perror("open device buttons"); exit(1); } /*永读按键并打印键值和状态*/ for (;;) { char current_buttons[6]; int count_of_changed_key; int i; /*使用 read 函数读取一组按键值(6 个)*/ if (read(buttons_fd, current_buttons, sizeof current_buttons) != sizeof current_buttons) { perror("read buttons:"); exit(1); } /*逐个分析读取到的按键值*/ for (i = 0, count_of_changed_key = 0; i < sizeof buttons / sizeof buttons[0]; i++) { if (buttons[i] != current_buttons[i]) { buttons[i] = current_buttons[i]; /*打印按键值,并标明按键按下/抬起的状态*/ printf("%skey %d is %s", count_of_changed_key? ", ": "", i+1, buttons[i] == '0' ? "up" : "down"); count_of_changed_key++; } } if (count_of_changed_key) { printf("\n"); } }

/*关闭按键设备文件*/ close(buttons_fd); return 0; }

在源代码所在的命令行输入命令: #arm-linux-gcc –o btn-test btn-test.c 这样就生成了 btn-test 可执行二进制文件,使用优盘或者网络工具( 比如 ftp)把它复制

到开发板中,并修改该文件的属性为可执行文件,然后运行它以测试,结果如图所示:

3.24 添加 PWM 控制蜂鸣器驱动

3.24.1 硬件解析

Mini2440 板带有一个蜂鸣器,它是由 PWM 控制的,下面是它的连接原理图:

Administrator
铅笔

可以看出,蜂鸣器所用的 GPB0 端口复用的功能为 TOUT0,它其实也就是 PWM 输出。

这在 S3C2440 手册中可以看到:

因此,我们需要在驱动程序中,首先把 GPB0 端口设置为 PWM 功能输出,再设定相

应的 Timer 就可以控制 PWM 的输出频率了。

3.24.2 编写添加驱动程序

在 linux-2.6.32.2/drivers/char/目录下,增加一个驱动程序文件 mini2440_pwm.c,内容如

下: 说明:以下驱动程序由友善之臂创建,网友 garby 对此驱动做了精彩的注解,原文链

接如下: http://blog.csdn.net/garby2004/archive/2009/09/28/4604039.aspx

#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/interrupt.h> #include <linux/gpio.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <plat/regs-timer.h> #include <mach/regs-irq.h> #include <asm/mach/time.h> #include <linux/clk.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/miscdevice.h> #define DEVICE_NAME "pwm" //设备名 #define PWM_IOCTL_SET_FREQ 1 //定义宏变量,用于后面的 ioctl 中的 switch case #define PWM_IOCTL_STOP 0 //定义信号量 lock static struct semaphore lock; /* freq: pclk/50/16/65536 ~ pclk/50/16 * if pclk = 50MHz, freq is 1Hz to 62500Hz * human ear : 20Hz~ 20000Hz

*/ static void PWM_Set_Freq( unsigned long freq ) //设置 pwm 的频率,配置各个寄存器 { unsigned long tcon; unsigned long tcnt; unsigned long tcfg1; unsigned long tcfg0; struct clk *clk_p; unsigned long pclk; //set GPB0 as tout0, pwm output 设置 GPB0 为 tout0,pwm 输出 s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPB0_TOUT0); tcon = __raw_readl(S3C2410_TCON); //读取寄存器 TCON 到 tcon tcfg1 = __raw_readl(S3C2410_TCFG1); //读取寄存器 TCFG1 到 tcfg1 tcfg0 = __raw_readl(S3C2410_TCFG0); //读取寄存器 TCFG0 到 tcfg0 //prescaler = 50 tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; // S3C2410_TCFG_PRESCALER0_MASK定时器 0和1 的预分频值的掩码,TCFG[0~8] tcfg0 |= (50 - 1); // 预分频为 50 //mux = 1/16 tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK; //S3C2410_TCFG1_MUX0_MASK 定时器 0 分割值的掩码:

TCFG1[0~3] tcfg1 |= S3C2410_TCFG1_MUX0_DIV16; //定时器 0 进行 16 分割 __raw_writel(tcfg1, S3C2410_TCFG1); //把 tcfg1 的值写到分割寄存器 S3C2410_TCFG1 中 __raw_writel(tcfg0, S3C2410_TCFG0); //把 tcfg0 的值写到预分频寄存器 S3C2410_TCFG0 中 clk_p = clk_get(NULL, "pclk"); //得到 pclk pclk = clk_get_rate(clk_p); tcnt = (pclk/50/16)/freq; //得到定时器的输入时钟,进而设置 PWM 的调制频率 __raw_writel(tcnt, S3C2410_TCNTB(0)); //PWM 脉宽调制的频率等于定时器的输入时钟 __raw_writel(tcnt/2, S3C2410_TCMPB(0)); //占空比是 50% tcon &= ~0x1f;

tcon |= 0xb; //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0 __raw_writel(tcon, S3C2410_TCON); //把 tcon 写到计数器控制寄存器 S3C2410_TCON 中 tcon &= ~2; //clear manual update bit __raw_writel(tcon, S3C2410_TCON); } static void PWM_Stop(void) { s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT); //设置 GPB0 为输出 s3c2410_gpio_setpin(S3C2410_GPB(0), 0); //设置 GPB0 为低电平,使蜂鸣器停止 } static int s3c24xx_pwm_open(struct inode *inode, struct file *file) { if (!down_trylock(&lock)) //是否获得信号量,是 down_trylock(&lock)=0,否则非 0 return 0; else return -EBUSY; //返回错误信息:请求的资源不可用 } static int s3c24xx_pwm_close(struct inode *inode, struct file *file) { PWM_Stop(); up(&lock); //释放信号量 lock return 0; } /*cmd 是 1,表示设置频率;cmd 是 2 ,表示停止 pwm*/ static int s3c24xx_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case PWM_IOCTL_SET_FREQ: //if cmd=1 即进入 case PWM_IOCTL_SET_FREQ if (arg == 0) //如果设置的频率参数是 0 return -EINVAL; //返回错误信息,表示向参数传递了无效的参数 PWM_Set_Freq(arg); //否则设置频率 break;

case PWM_IOCTL_STOP: // if cmd=2 即进入 case PWM_IOCTL_STOP PWM_Stop(); //停止蜂鸣器 break; } return 0; //成功返回 } /*初始化设备的文件操作的结构体*/ static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = s3c24xx_pwm_open, .release = s3c24xx_pwm_close, .ioctl = s3c24xx_pwm_ioctl, }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; static int __init dev_init(void) { int ret; init_MUTEX(&lock); //初始化一个互斥锁 ret = misc_register(&misc); //注册一个 misc 设备 printk (DEVICE_NAME"\tinitialized\n"); return ret; } static void __exit dev_exit(void) { misc_deregister(&misc); //注销设备 } module_init(dev_init);

module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("FriendlyARM Inc."); MODULE_DESCRIPTION("S3C2410/S3C2440 PWM Driver");

以上驱动程序中,一些关键词的解释和说明如下:

1 CPU 计数器控制寄存器 1)配置定时器输入时钟 TCFG0-时钟配置寄存器 0,用于获得预分频值(1~255) TCFG1-时钟配置寄存器 1,用于获得分割值(2,4,8,16,32) 定时器输入时钟频率=PLCK/{预分频+1}/{分割值} 2)配置 PWM 的占空比 TCNTB0-定时器 0 计数缓存寄存器 ,是由定时器的输入时钟分频得到,是脉宽调制的

频率 TCMTB0-定时器 0 比较缓存寄存器 ,用于设定 PWM 的占空比 ,寄存器值为高定平

的 假设 TCNTB0 的频率是 160,如果 TCMTB0 是 110,则 PWM 在 110 个周期是高定平,

50 周期是低电平,从而占空比为 11:5 3)定时器控制寄存器 TCON TCON[0~4]用于控制定时器 0

2.读写寄存器的函数:__raw_readl 和__raw_writel 读端口寄存器用__raw_readl(a ),该函数从端口 a 返回一个 32 位的值。相关的定义在

include/asm-arm/io.h 中。#define __raw_readl(a) (*(volatile unsigned int*)(a)),写端口寄存器用

__raw_writel(v,a),该函数将一个 32 位的值写入端口 a 中。相关的定义在 include/asm-arm/io.h 中。#define __raw_writel(v,a) (*(volatile unsigned int*)(a) = (v))。此处设置功能控制寄存器,将

相应的引脚设为输出状态。

3.内核中操作 gpio gpio_cfgpin 配置相应 GPIO 口的功能 gpio_setpin IO 口为输出功能时,写引脚

4.内核中基于信号量的 Llinux 的并发控制 在驱动程序中,当多个线程同时访问相同的资源时,可能会引发“竞态”,因此必须对

共享资源进行并发控制。信号量(绝大多数作为互斥锁使用)是一种进行并发控制的手段(还

有自旋锁,它适合于保持时间非常短的时间)。信号量只能在进程的上下文中使用。 void init_MUTEX(&lock)初始化一个互斥锁,即他把信号量 lock 设置为 1 void up (&lock) 释放信号量,唤醒等待者 int down_trylock(&lock) 尝试获得信号量 lock ,如果能够立刻获得,就获得信号量,

Administrator
铅笔
Administrator
铅笔

并返回为 0.否则返回非 0.并且它不会导致休眠,可以在中断上下文中使用。在 PWM 中,当计

数值溢出时,就会引发计数中断。所以在这里用这个函数来获得信号。

3.24.3 把驱动程序加入内核

接下来,打开 linux-2.6.32.2/drivers/char/Kconfig 文件,加入如下红色部分内容: config MINI2440_BUTTONS tristate "Buttons driver for FriendlyARM Mini2440 development boards" depends on MACH_MINI2440 default y if MACH_MINI2440 help this is buttons driver for FriendlyARM Mini2440 development boards config MINI2440_BUZZER tristate "Buzzer driver for FriendlyARM Mini2440 development boards" depends on MACH_MINI2440 default y if MACH_MINI2440 help this is buzzer driver for FriendlyARM Mini2440 development boards config MINI2440_ADC bool "ADC driver for FriendlyARM Mini2440 development boards" depends on MACH_MINI2440 default y if MACH_MINI2440 help this is ADC driver for FriendlyARM Mini2440 development boards Notes: the touch-screen-driver required this option

再打开 linux-2.6.32.2/drivers/char/Makefile,把该驱动程序的目标文件根据配置定义

加入,如下红色部分: obj-$(CONFIG_JS_RTC) += js-rtc.o js-rtc-y = rtc.o obj-$(CONFIG_LEDS_MINI2440) += mini2440_leds.o obj-$(CONFIG_MINI2440_BUTTONS) += mini2440_buttons.o obj-$(CONFIG_MINI2440_BUZZER) += mini2440_pwm.o obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o # Files generated that shall be removed upon make clean clean-files := consolemap_deftbl.c defkeymap.c

这样,我们就在内核中加入了 PWM 控制蜂鸣器的驱动程序

3.24.4 配置编译新内核

接上面的步骤,在内核源代码目录下执行:make menuconfig 重新配置内核,依次选

择进入如下子菜单项: Device Drivers ---> Character devices --->

如题,进入 PWM 控制蜂鸣器的配置菜单:

在此按空格键选择“<*> Buzzer driver for FriendlyARM Mini2440 development boards

(NEW)”选项,并退出保存内核配置。 在内核源代码根目录下执行;make zImage,把生成的新内核烧写到开发板中。

3.24.5 测试 PWM 控制蜂鸣器

为了测试上面的按键驱动,创建一个按键测试程序:pwm.c,内容如下: 提示:光盘中已经提供了按键测试的源代码程序,它位于\linux 示例代码\examples\pwm

目录中,源代码名为:pwm_test.c,在此重新命名为 pwm.c 以作区分。 说明:以下测试程序由友善之臂创建,网友 garby 对此驱动做了精彩的注解,原文链

接如下: http://blog.csdn.net/garby2004/archive/2009/09/28/4604039.aspx

#include <stdio.h> //标准输入输出定义 #include <termios.h> //POSIX 终端控制定义 #include <unistd.h> //Unix 标准函数定义 #include <stdlib.h> //标准函数库定义 #define PWM_IOCTL_SET_FREQ 1 #define PWM_IOCTL_STOP 0 #define ESC_KEY 0x1b //定义 ESC_KEY 为 ESC 按键的键值 static int getch(void) //定义函数在终端上获得输入,并把输入的量(int)返回 { struct termios oldt,newt; //终端结构体 struct termios int ch; if (!isatty(STDIN_FILENO)) { //判断串口是否与标准输入相连 fprintf(stderr, "this problem should be run at a terminal\n"); exit(1); } // save terminal setting if(tcgetattr(STDIN_FILENO, &oldt) < 0) { //获取终端的设置参数 perror("save the terminal setting"); exit(1); } // set terminal as need newt = oldt; newt.c_lflag &= ~( ICANON | ECHO ); //控制终端编辑功能参数 ICANON 表示使用标准输入模式;参数

ECH0 表示显示输入字符 if(tcsetattr(STDIN_FILENO,TCSANOW, &newt) < 0) { //保存新的终端参数 perror("set terminal"); exit(1); } ch = getchar(); // restore termial setting

Administrator
在文本上注释
POSIX 表示可移植操作系统接口标准

if(tcsetattr(STDIN_FILENO,TCSANOW,&oldt) < 0) { //恢复保存旧的终端参数 perror("restore the termial setting"); exit(1); } return ch; } static int fd = -1; static void close_buzzer(void); static void open_buzzer(void) //打开蜂鸣器 { fd = open("/dev/pwm", 0); //打开 pwm 设备驱动文件 if (fd < 0) { perror("open pwm_buzzer device"); //打开错误,则终止进程。退出参数为 1 exit(1); } // any function exit call will stop the buzzer atexit(close_buzzer); //退出回调 close_buzzer } static void close_buzzer(void) //关闭蜂鸣器 { if (fd >= 0) { ioctl(fd, PWM_IOCTL_STOP); //停止蜂鸣器 close(fd); //关闭设备驱动文件 fd = -1; } } static void set_buzzer_freq(int freq) { // this IOCTL command is the key to set frequency int ret = ioctl(fd, PWM_IOCTL_SET_FREQ, freq); //设置频率 if(ret < 0) { //如果输入的频率错误 perror("set the frequency of the buzzer"); exit(1); //退出,返回 1 } }

static void stop_buzzer(void) { int ret = ioctl(fd, PWM_IOCTL_STOP); //关闭蜂鸣器 if(ret < 0) { //如果无法关闭蜂鸣器 perror("stop the buzzer"); exit(1); //退出返回 1 } } int main(int argc, char **argv) { int freq = 1000 ; open_buzzer();//打开蜂鸣器 printf( "\nBUZZER TEST ( PWM Control )\n" ); printf( "Press +/- to increase/reduce the frequency of the BUZZER\n" ) ; printf( "Press 'ESC' key to Exit this program\n\n" ); while( 1 ) { int key; set_buzzer_freq(freq); //设置蜂鸣器频率 printf( "\tFreq = %d\n", freq ); key = getch(); switch(key) { case '+': if( freq < 20000 ) freq += 10; break; case '-': if( freq > 11 ) freq -= 10 ; break;

case ESC_KEY: case EOF: stop_buzzer(); exit(0); default: break; } } }

在 pwm.c 所在的目录命令行下输入: #arm-linux-gcc –o pwm pwm.c 这样就生成了 pwm 可执行二进制文件,使用优盘或者网络工具( 比如 ftp)把它复制到

开发板中,并修改该文件的属性为可执行文件,然后运行它以测试,结果如图所示。 说明:测试时可以通过按下键盘上的“+”/“-”以改变 PWM 输出的频率。

总结:至此,我们已经从 原始的 Linux-2.6.32.2 内核开始,完成了 mini2440 开发板

几乎所有的驱动程序移植和开发,或许你对其中的某些原理还不是特别了解,但对于初学者而

言,要想在几天内理解所有内容也是不太可能的事情。不管怎样,如果你按照上面的步骤都顺

利走下来,至少也证明了你真的想进入这个行业,并为之投入了精力和热情,也会了解到很多

嵌入式 Linux 底层开发一些概念和方法;更多的知识和细节需要你不断的去探索和思考。对于

已经参加工作的同学而言,有时因为项目的紧急开发,我们都不能来得及深入研究更多,自己

也不清楚为何这样或者那样就可以正常运转了。这些都是我们不断成长的过程,随着阅历以及

经验的不断丰富,我们或许会有一天突然明白我们所从事的和所热爱的目的和源泉;那就让我

们从现在开始创造更美好的明天吧。

第四章 关于文件系统

4.1 友善之臂 mini2440 root_qtopia 文件系统启动过程分析

本文简介: 友善之臂提供的根文件系统十分具有创新意义,其功能之强大,先进,实用至今保持在

领先地位,网友 kasim 对其作了详尽的剖析,道出了很多“秘密”,对于任何致力于嵌入式 Linux开发的人员是不可多得的好资料,现整理如下。

原文网址: http://www.arm9home.net/read.php?tid-1702.html

下面是这篇文章的内容,我们对此进行了简单的编辑和修改:

对于 mini2440 新的 root_qtopia 文件系统启动过程,我在这里做了一些简单的分析,

和大家分享一下经验,不足之处也请大家及时指出。

其实,虽然 root_qtopia 这个文件系统的 GUI 是基于 Qtopia 的,但其初始化启动过程

却是由大部分由 busybox 完成,Qtopia(qpe)只是在启动的 后阶段被开启。

由于默认的内核命令行上有 init=/linuxrc, 因此,在文件系统被挂载后,运行的第一个程

序是根目录下的 linuxrc。 这是一个指向/bin/busybox 的链接,也就是说,系统起来后运行的

第一个程序也就是 busybox 本身。

这种情况下,busybox 首先将试图解析/etc/inittab 来获取进一步的初始化配置信息(参

考 busybox 源代码 init/init.c 中的 parse_inittab()函数)。而事实上,root_qtopia 中并没有/etc/inittab 这个配置文件,根据 busybox 的逻辑,它将生成默认的配置

复制代码

Administrator
在文本上注释
Qtopia 是Trolltech 公司为采用嵌入式Linux 操作系统的消费电子设备而开发的综合应用平台, Qtopia包含完整的应用层、灵活的用户界面、窗口操作系统、应用程序启动程序以及开发框架。
Administrator
在文本上注释
图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。
Administrator
在文本上注释
BusyBox就好像是个大工具箱,它集成压缩了 Linux 的许多工具和命令。

1. static void parse_inittab(void)

2. {

3. #if ENABLE_FEATURE_USE_INITTAB

4. char *token[4];

5. parser_t *parser = config_open2("/etc/inittab", fopen_for_read);

6.

7. if (parser == NULL)

8. #endif

9. {

10. /* No inittab file -- set up some default behavior */

11. /* Reboot on Ctrl-Alt-Del */

12. new_init_action(CTRLALTDEL, "reboot", "");

13. /* Umount all filesystems on halt/reboot */

14. new_init_action(SHUTDOWN, "umount -a -r", "");

15. /* Swapoff on halt/reboot */

16. if (ENABLE_SWAPONOFF)

17. new_init_action(SHUTDOWN, "swapoff -a", "");

18. /* Prepare to restart init when a QUIT is received */

19. new_init_action(RESTART, "init", "");

20. /* Askfirst shell on tty1-4 */

21. new_init_action(ASKFIRST, bb_default_login_shell, "");

22. //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users

23. new_init_action(ASKFIRST, bb_default_login_shell, VC_2);

24. new_init_action(ASKFIRST, bb_default_login_shell, VC_3);

25. new_init_action(ASKFIRST, bb_default_login_shell, VC_4);

26. /* sysinit */

27. new_init_action(SYSINIT, INIT_SCRIPT, "");

28. return;

29. }

其中, 重要的一个,就是 new_init_action(SYSINIT, INIT_SCRIPT, ""),也就决定了

接下去初始化的脚本是 INIT_SCRIPT 所定义的值。这个宏的默认值是"/etc/init.d/rcS".

下面是文件系统中/etc/init.d/rcS 的内容, 也是我们要分析的重点

复制代码

1. #! /bin/sh

2.

3. PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:

4. runlevel=S

5. prevlevel=N

6. umask 022

7. export PATH runlevel prevlevel

8.

9. #

10. # Trap CTRL-C &c only in this shell so we can interrupt subprocesses.

11. #

12. trap ":" INT QUIT TSTP

13. /bin/hostname FriendlyARM

14.

15. /bin/mount -n -t proc none /proc

16. /bin/mount -n -t sysfs none /sys

17. /bin/mount -n -t usbfs none /proc/bus/usb

18. /bin/mount -t ramfs none /dev

19.

20. echo /sbin/mdev > /proc/sys/kernel/hotplug

21. /sbin/mdev -s

22. /bin/hotplug

23. # mounting file system specified in /etc/fstab

24. mkdir -p /dev/pts

25. mkdir -p /dev/shm

26. /bin/mount -n -t devpts none /dev/pts -o mode=0622

27. /bin/mount -n -t tmpfs tmpfs /dev/shm

28. /bin/mount -n -t ramfs none /tmp

29. /bin/mount -n -t ramfs none /var

30. mkdir -p /var/empty

31. mkdir -p /var/log

32. mkdir -p /var/lock

33. mkdir -p /var/run

34. mkdir -p /var/tmp

35.

36. /sbin/hwclock -s

37.

38. syslogd

39. /etc/rc.d/init.d/netd start

40. echo " " > /dev/tty1

41. echo "Starting networking..." > /dev/tty1

42. sleep 1

43. /etc/rc.d/init.d/httpd start

44. echo " " > /dev/tty1

45. echo "Starting web server..." > /dev/tty1

46. sleep 1

47. /etc/rc.d/init.d/leds start

48. echo " " > /dev/tty1

49. echo "Starting leds service..." > /dev/tty1

50. echo " "

51. sleep 1

52.

53. /sbin/ifconfig lo 127.0.0.1

54. /etc/init.d/ifconfig-eth0

55.

56. /bin/qtopia &

57. echo " " > /dev/tty1

58. echo "Starting Qtopia, please waiting..." > /dev/tty1

下面就逐个来分析:

复制代码

1. PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:

2. runlevel=S

3. prevlevel=N

4. umask 022

5. export PATH runlevel prevlevel

为启动环境设置必要的环境变量:

复制代码

1. /bin/hostname FriendlyARM

设置机器名字;:

复制代码

1. /bin/mount -n -t proc none /proc

2. /bin/mount -n -t sysfs none /sys

3. /bin/mount -n -t usbfs none /proc/bus/usb

4. /bin/mount -t ramfs none /dev

挂载“虚拟”文件系统“/proc”和“/sys”,并且在/dev 目录上挂载一个 ramfs,相当于

把原本 NAND Flash 上的只读的/dev 目录“覆盖”上一块可写的空的 SDRAM。

这里要注意的是,/sys 和挂载了 ramfs 的/dev 是正确创建设备节点的关键。对于 2.6.29 内核来说,已经没有了 devfs 的支持,创建设备节点只有通过两种办法由文件系统完成:

1) 制作文件系统镜像前用 mknod 手动创建好系统中所有的(包括可能有的)设备节点,

并把这些节点文件一起做进文件系统镜像中;

2)在文件系统初始化过程中,通过/sys 目录所输出的信息,在/dev 目录下动态的创建

系统中当前实际有的设备节点。

显然,方法 1)有很大的局限性,仅限于没有设备动态增加或减少的情况,不适用于很

多设备热插拔的情况,比如 U 盘,SD 卡等等。方法 2)是目前大多数 PC 上 Linux 的做法(基

于 udev 实现)。这种方法有两个前提: /sys 目录挂载和一个可写的/dev 目录。 这也就是为

什么我们在这里需要挂载/sys 和 ramfs 在/dev 目录上。事实上,这种方法 早就是为热插拔

设计的, 你可以理解为当系统启动是,所有设备一下子全部“插入”了进来。

这里有一点要说明的是,在文件系统初始化跑到这里之前,原本的/dev 目录下必须有

一个的设备节点:/dev/console。

其实,要搞清楚“程序”这种东西,没有什么好的办法,无非两个东西,一是看源代码,

二是看脚本(其实还是一个东西,看脚本也是为了看脚本调用了什么程序

复制代码

1. echo /sbin/mdev > /proc/sys/kernel/hotplug

2. /sbin/mdev -s

3. /bin/hotplug

这几个就是用来完成我上面所说的两个东西: 1)通过 mdev -s 在/dev 目录下建立必

要的设备节点; 2)设置内核的 hotplug handler 为 mdev, 即当设备热插拔时,由 mdev 接收

来自内核的消息并作出相应的回应, 比如挂载 U 盘。

对于 mdev,需要注意的是,文件系统里存在/etc/mdev.conf 文件,它包含了 mdev 的

配置信息。通过这个文件,我们可以自定义一些设备节点的名称或链接来满足特定的需要。这

是 root qtopia 中 mdev.conf 的内容:

复制代码

1. # system all-writable devices

2. full 0:0 0666

3. null 0:0 0666

4. ptmx 0:0 0666

5. random 0:0 0666

6. tty 0:0 0666

7. zero 0:0 0666

8.

9. # console devices

10. tty[0-9]* 0:5 0660

11. vc/[0-9]* 0:5 0660

12.

13. # serial port devices

14. s3c2410_serial0 0:5 0666 =ttySAC0

15. s3c2410_serial1 0:5 0666 =ttySAC1

16. s3c2410_serial2 0:5 0666 =ttySAC2

17. s3c2410_serial3 0:5 0666 =ttySAC3

18.

19. # loop devices

20. loop[0-9]* 0:0 0660 =loop/

21.

22. # i2c devices

23. i2c-0 0:0 0666 =i2c/0

24. i2c-1 0:0 0666 =i2c/1

25.

26. # frame buffer devices

27. fb[0-9] 0:0 0666

28.

29. # input devices

30. mice 0:0 0660 =input/

31. mouse.* 0:0 0660 =input/

32. event.* 0:0 0660 =input/

33. ts.* 0:0 0660 =input/

34.

35. # rtc devices

36. rtc0 0:0 0644 >rtc

37. rtc[1-9] 0:0 0644

38.

39. # misc devices

40. mmcblk0p1 0:0 0600 =sdcard */bin/hotplug

41. sda1 0:0 0600 =udisk * /bin/hotplug

可以看到,原本串口驱动注册的设备名是 s3c2410_serial0, s3c2410_serial1 和

s3c2410_serial2,而 mdev 则会在/dev 目录下对应生成 ttySAC0, ttySAC1 和 ttySAC2 以符合

应用程序对于串口设备名称的习惯。同样的,/dev/sdcard 和/dev/udisk 永远分别指向 SD 卡和

U 盘的第一个分区。(所以,用那些没有分区表的 SD 卡或 U 盘的兄弟知道原因了吧...)

复制代码

1. # mounting file system specified in /etc/fstab

2. mkdir -p /dev/pts

3. mkdir -p /dev/shm

4. /bin/mount -n -t devpts none /dev/pts -o mode=0622

5. /bin/mount -n -t tmpfs tmpfs /dev/shm

6. /bin/mount -n -t ramfs none /tmp

7. /bin/mount -n -t ramfs none /var

8. mkdir -p /var/empty

9. mkdir -p /var/log

10. mkdir -p /var/lock

11. mkdir -p /var/run

12. mkdir -p /var/tmp

就像注释中所说的,这是用来挂载其他一些常用的文件系统,并在/var 目录下(同样是

ramfs,可写的)新建必要的目录。

复制代码

1. /sbin/hwclock -s

用来设定系统时间的,从硬件 RTC 中获取,要获取正确的时间,必须先设置好正确的

时间(如何设置 RTC 见用户手册说明),目前友善之臂的开发板出厂时并没有设置实际的时

间,而是系统默认的。

接下来就是启动系统服务了,包括 log 记录,网络, http server 和自定义的"跑马灯服

务"...

关于 mdev.conf 中的一点补充说明:

复制代码

1. # misc devices

2. mmcblk0p1 0:0 0600 =sdcard */bin/hotplug

3. sda1 0:0 0600 =udisk * /bin/hotplug

这两句配置的意思是当 SD 卡或者 U 盘插入/拔出时,将这个消息传递给自定义的热插

拔 handler, /bin/hotplug. 这个程序是友善之臂开发的用于自动挂载可移动设备的,目前是 SD卡和 U 盘。它的逻辑很简单,将 SD 卡或者 U 盘的第一个分区作为 FAT/FAT32 挂载到/sdcard或者/udisk.

但这也同时带来一个问题,当 SD 卡或者 U 盘上没有分区表或者第一个分区不是 FAT/FAT32 格式的时候,它就玩不转了:)

这是/bin/hotplug 里的二进制数据片段,可以看到我上面说的逻辑:

000010d0h: 52 00 00 00 4D 00 00 00 00 00 00 00 41 00 00 00 ; R...M.......A...

000010e0h: 43 00 00 00 54 00 00 00 49 00 00 00 4F 00 00 00 ; C...T...I...O...

000010f0h: 4E 00 00 00 00 00 00 00 44 00 00 00 45 00 00 00 ; N.......D...E...

00001100h: 56 00 00 00 4E 00 00 00 41 00 00 00 4D 00 00 00 ; V...N...A...M...

00001110h: 45 00 00 00 00 00 00 00 61 00 00 00 64 00 00 00 ; E.......a...d...

00001120h: 64 00 00 00 00 00 00 00 72 00 00 00 65 00 00 00 ; d.......r...e...

00001130h: 6D 00 00 00 6F 00 00 00 76 00 00 00 65 00 00 00 ; m...o...v...e...

00001140h: 00 00 00 00 2F 00 00 00 64 00 00 00 65 00 00 00 ; ..../...d...e...

00001150h: 76 00 00 00 2F 00 00 00 75 00 00 00 64 00 00 00 ; v.../...u...d...

00001160h: 69 00 00 00 73 00 00 00 6B 00 00 00 00 00 00 00 ; i...s...k.......

00001170h: 2F 00 00 00 64 00 00 00 65 00 00 00 76 00 00 00 ; /...d...e...v...

00001180h: 2F 00 00 00 73 00 00 00 64 00 00 00 63 00 00 00 ; /...s...d...c...

00001190h: 61 00 00 00 72 00 00 00 64 00 00 00 00 00 00 00 ; a...r...d.......

000011a0h: 4D 00 00 00 44 00 00 00 45 00 00 00 56 00 00 00 ; M...D...E...V...

000011b0h: 00 00 00 00 6D 00 00 00 6D 00 00 00 63 00 00 00 ; ....m...m...c...

000011c0h: 62 00 00 00 6C 00 00 00 6B 00 00 00 30 00 00 00 ; b...l...k...0...

000011d0h: 70 00 00 00 31 00 00 00 00 00 00 00 73 00 00 00 ; p...1.......s...

000011e0h: 64 00 00 00 61 00 00 00 31 00 00 00 00 00 00 00 ; d...a...1.......

000011f0h: 76 00 00 00 66 00 00 00 61 00 00 00 74 00 00 00 ; v...f...a...t...

00001200h: 00 00 00 00 2F 00 00 00 64 00 00 00 65 00 00 00 ; ..../...d...e...

00001210h: 76 00 00 00 2F 00 00 00 77 00 00 00 61 00 00 00 ; v.../...w...a...

00001220h: 74 00 00 00 63 00 00 00 68 00 00 00 64 00 00 00 ; t...c...h...d...

00001230h: 6F 00 00 00 67 00 00 00 00 00 00 00 9A B2 01 81 ; o...g.......毑.?

00001240h: B0 ; ?

复制代码

1. syslogd

2. /etc/rc.d/init.d/netd start

3. echo " " > /dev/tty1

4. echo "Starting networking..." > /dev/tty1

5. sleep 1

6. /etc/rc.d/init.d/httpd start

7. echo " " > /dev/tty1

8. echo "Starting web server..." > /dev/tty1

9. sleep 1

10. /etc/rc.d/init.d/leds start

11. echo " " > /dev/tty1

12. echo "Starting leds service..." > /dev/tty1

13. echo " "

14. sleep 1

启动一系列服务:

syslog - 用于记录内核和应用程序 debug 信息

netd - inetd, 一个挂载启动各种网络相关服务的看守进程

httpd - http server 看守进程

leds - 跑马灯看守进程

其中,inetd 的配置文件为/etc/inetd.conf,这是文件内容

复制代码

1. # /etc/inetd.conf: see inetd(8) for further informations.

2. echo stream tcp nowait root internal

3. echo dgram udp wait root internal

4. daytime stream tcp nowait root internal

5. daytime dgram udp wait root internal

6. time stream tcp nowait root internal

7. time dgram udp wait root internal

8.

9. # These are standard services.

10. #

11. ftp stream tcp nowait root /usr/sbin/ftpd /usr/sbin/ftpd

12. telnet stream tcp nowait root /usr/sbin/telnetd /usr/sbin/telnetd -i

可以看到,这里启动的网络服务有两个: 1)ftp server 和 2)telnet server。有关网络

服务的端口和协议等具体信息,可以参考/etc/services, /etc/protocols

再接下来

复制代码

1. /sbin/ifconfig lo 127.0.0.1

2. /etc/init.d/ifconfig-eth0

配置网络设备(网卡):

1)设定本机回环地址为 127.0.0.1

2)运行网卡设置脚本/etc/init.d/ifconfig-eth0

这是/etc/init.d/ifconfig-eth0 的内容, 加入了我的一些注释

复制代码

1. #!/bin/sh

2.

3. echo -n Try to bring eth0 interface up......>/dev/ttySAC0

4.

5. #判断/etc/eth0-setting 文件是否存在

6. if [ -f /etc/eth0-setting ] ; then

7. #读取配置文件信息

8. source /etc/eth0-setting

9.

10. #如果根文件系统为 nfs,则说明网卡已经配置 OK,这里什么都不需要配置了

11. if grep -q "^/dev/root / nfs " /etc/mtab ; then

12. echo -n NFS root ... > /dev/ttySAC0

13. #否则,根据配置文件中的 MAC, IP, Mask 和 Gateway 通过 ifconfig 命令相应地配置网卡

14. else

15. ifconfig eth0 down

16. ifconfig eth0 hw ether $MAC

17. ifconfig eth0 $IP netmask $Mask up

18. route add default gw $Gateway

19. fi

20.

21. #将配置文件中的 DNS 设置写入/etc/resolv.conf 使之生效

22. echo nameserver $DNS > /etc/resolv.conf

23. #配置文件不存在,使用默认配置

24. else

25.

26. #如果根文件系统为 nfs,则说明网卡已经配置 OK,这里什么都不需要配置了

27. if grep -q "^/dev/root / nfs " /etc/mtab ; then

28. echo -n NFS root ... > /dev/ttySAC0

29. else

30. #将网卡的 IP 地址设定为 192.168.1.230

31. /sbin/ifconfig eth0 192.168.1.230 netmask 255.255.255.0 up

32. fi

33. fi

34.

35. echo Done > /dev/ttySAC0

可以看到,NFS 自动识别就是靠判断/etc/mtab 中是否有 nfs 的挂载记录实现的。

这是 root qtopia 文件系统中/etc/eth0-settings 文件

复制代码

1. IP=192.168.1.230

2. Mask=255.255.255.0

3. Gateway=192.168.1.1

4. DNS=192.168.1.1

5. MAC=08:90:90:90:90:90

终于到 后了,启动 Qtopia GUI 环境

复制代码

1. /bin/qtopia &

2. echo " " > /dev/tty1

3. echo "Starting Qtopia, please waiting..." > /dev/tty1

可以看到,这里 Qtopia 是通过运行/bin/qtopia 来启动的。事实上,/bin/qtopia 也是一

个脚本,它的任务是设定 Qtopia 运行必要的环境, 后通过调用 qpe 可执行文件真正启动

Qtopia。这是它的全部内容,我加入了一些注释:

复制代码

1. #!/bin/sh

2.

3. #tslib 环境变量设置,包括了 touchscreen 设备文件,tslib 配置文件,tslib plug-in 位置和 touchscreen 校准数据

文件

4. export TSLIB_TSDEVICE=/dev/input/event0

5. export TSLIB_CONFFILE=/usr/local/etc/ts.conf

6. export TSLIB_PLUGINDIR=/usr/local/lib/ts

7. export TSLIB_CALIBFILE=/etc/pointercal

8. #Qtopia 环境变量设置,设定了 Qtopia 主要文件位置

9. export QTDIR=/opt/Qtopia

10. export QPEDIR=/opt/Qtopia

11. #设定 PATH 和 LD_LIBRARY_PATH 以包含 Qtopia 的可执行文件和共享库文件,方便 Qtopia 正确运行

12. export PATH=$QTDIR/bin:$PATH

13. export LD_LIBRARY_PATH=$QTDIR/lib:/usr/local/lib:$LD_LIBRARY_PATH

14.

15. #通过判断/sys/devices/virtual/input/input0/uevent 中是否包含 touchscreen 信息使 Qtopia 自动识别 touchscreen

和 USB 鼠标

16. TS_INFO_FILE=/sys/devices/virtual/input/input0/uevent

17. if [ -e $TS_INFO_FILE -a "/bin/grep -q TouchScreen < $TS_INFO_FILE" ]; then

18. export QWS_MOUSE_PROTO="TPanel:/dev/input/event0 USB:/dev/input/mice"

19. if [ -e /etc/pointercal -a ! -s /etc/pointercal ] ; then

20. rm /etc/pointercal

21. fi

22. else

23. export QWS_MOUSE_PROTO="USB:/dev/input/mice"

24. >/etc/pointercal

25. fi

26. unset TS_INFO_FILE

27.

28. export QWS_KEYBOARD=TTY:/dev/tty1

29. export KDEDIR=/opt/kde

30.

31. export HOME=/root

32.

33. #通过调用/opt/Qtopia/bin/qpe 真正启动 Qtopia

34. exec $QPEDIR/bin/qpe 1>/dev/null 2>/dev/null

到此为止,文件系统从初始化到 终启动 Qtopia GUI 环境的全部过程就结束了,大家

可以看到,友善之臂的“小秘密”其实都在这里,说穿了很简单:)只要大家能够静下心来认真看

看脚本,看看源代码,加上一些背景知识的了解,搞清楚一个嵌入式系统就这么简单

4.2 使用 Busybox 构建文件系统

说明:本文主要基于网友 huang-tomey 所写的“yaffs2 文件系统移植”一文,这里详细

介绍了仅使用 busybox 建立基本根文件系统的过程,实际上根据开发和项目的需要,一般基本

的文件系统是远远不够的,友善之臂提供的根文件系统是面向嵌入式爱好者精心配置的一个典

型示范,详细介绍可以参考 kasim 网友所写的“mini2440 root_qtopia 文件系统启动过程分析”

一文 原文网址:

http://huang-tomey.blog.163.com/blog/static/1247505732009916437175/ 下面是使用 busybox 制作基本根文件系统的详细步骤

4.2.1 下载 busybox 源代码

从 http://www.busybox.net/downloads/ 下 载 busybox , 这 里 下 载 的 是

busy busybox-1.13.3.tar.gz,这和当前mini2440 开发板使用的版本是一致的。

4.2.2 根文件系统目录说明

嵌入式 Linux 中都需要构建根文件系统,构建根文件系统的规则在 FHS(Filesystem Hierarchy Standard)文档中,下面是根文件系统顶层目录。 目录 内容 bin 存放所有用户都可以使用的、基本的命令。 sbin 存放的是基本的系统命令,它们用于启动系统、修复系统等。 usr 里面存放的是共享、只读的程序和数据。 proc 这是个空目录,常作为 proc 文件系统的挂载点。 dev 该目录存放设备文件和其它特殊文件。 etc 存放系统配置文件,包括启动文件。 lib 存放共享库和可加载块(即驱动程序),共享库用于启动系统、运行根文件系统中的可

执行程序。 boot 引导加载程序使用的静态文件 home 用户主目录,包括供服务账号锁使用的主目录,如 FTP mnt 用于临时挂接某个文件系统的挂接点,通常是空目录。也可以在里面创建空的子目

录。 opt 给主机额外安装软件所摆放的目录。 root root 用户的主目录 tmp 存放临时文件,通常是空目录。 var 存放可变的数据。

4.2.3 建立根文件系统目录

进入到/opt/studyarm 目录,新建建立根文件系统目录的脚本文件 create_rootfs_bash,使

用命令 chmod +x create_rootfs_bash 改变文件的可执行权限,./create_rootfs_bash 运行脚本,就

完成了根文件系统目录的创建。 #!/bin/sh echo "------Create rootfs directons start...--------" mkdir rootfs cd rootfs echo "--------Create root,dev....----------" mkdir root dev etc boot tmp var sys proc lib mnt home mkdir etc/init.d etc/rc.d etc/sysconfig mkdir usr/sbin usr/bin usr/lib usr/modules echo "make node in dev/console dev/null" mknod -m 600 dev/console c 5 1 mknod -m 600 dev/null c 1 3 mkdir mnt/etc mnt/jffs2 mnt/yaffs mnt/data mnt/temp mkdir var/lib var/lock var/run var/tmp chmod 1777 tmp chmod 1777 var/tmp echo "-------make direction done---------"

改变了 tmp 目录的使用权,让它开启 sticky 位,为 tmp 目录的使用权开启此位,可确保

tmp 目录底下建立的文件,只有建立它的用户有权删除。尽管嵌入式系统多半是单用户,不过

有些嵌入式应用不一定用 root 的权限来执行,因此需要遵照根文件系统权限位的基本规定来

设计。

4.2.4 建立动态链接库

动态链接库直接用友善之臂的,先解压友善之臂的根文件包,拷贝 lib 的内容到新建的

根文件目录 lib 内。 cd /mnt/hgfs/share tar –zxvf root_qtopia.tgz –C /opt/studyarm cp –rfd /opt/studyarm/root_qtopia/lib/* /opt/studyarm/rootfs/lib/*

4.2.5 交叉编译 Bosybox

Bosybox 是一个遵循 GPL v2 协议的开源项目,它在编写过程总对文件大小进行优化,

并考虑了系统资源有限(比如内存等)的情况,使用 Busybox 可以自动生成根文件系统所需的

bin、sbin、usr 目录和 linuxrc 文件。 1、解压 busybox cd /mnt/hgfs/share tar –zxvf busybox-1.13.3.tar.tgz –C /opt/studyarm 2、进入源码,修改 Makefile 文件: cd /opt/studyarm/busybox-1.13.3 修改: CROSS_COMPILE ?=arm-linux- //第 164 行 ARCH ?=arm //第 189 行 3、配置 busybox 提示:友善之臂已经在光盘中提供了 busybox 的源代码包,在光盘\linux 目录中,文件

名为:busybox-1.13.3-mini2440.tgz(用户手册 5.4 章节介绍了解压安装的方法),解压后里面包

含了友善之臂提供的缺省配置文件:fa_config(输入命令“cp fa.config .config”可以调用该配

置),一般用户直接使用缺省文件就可以了,这样生成的 busybox 和 root_qtopia 中的是完全一

致的。但为了对它的配置了解更多一些,可以参考原文作者的如下步骤: 输入 make menuconfig 进行配置 (1)、Busybox Settings---> General Configuration---> [*] Show verbose applet usage messages [*] Store applet usage messages in compressed form [*] Support –install [-s] to install applet links at runtime [*] Enable locale support(system needs locale for this to work)

[*] Support for –long-options [*] Use the devpts filesystem for unix98 PTYs [*] Support writing pidfiles [*] Runtime SUID/SGID configuration via /etc/busybox.config [*] Suppress warning message if /etc/busybox.conf is not readable Build Options---> [*] Build BusyBox as a static binary(no shared libs) [*] Build with Large File Support(for accessing files>2GB) Installation Options-> []Don’t use /usr Applets links (as soft-links) ---> (./_install) BusyBox installation prefix Busybox Library Tuning ---> (6)Minimum password legth (2)MD5:Trade Bytes for Speed [*]Fsater /proc scanning code(+100bytes) [*]Command line editing (1024)Maximum length of input [*] vi-style line editing commands (15) History size [*] History saving [*] Tab completion [*]Fancy shell prompts (4) Copy buffer size ,in kilobytes [*]Use ioctl names rather than hex values in error messages [*]Support infiniband HW (2)、Linux Module Utilities---> (/lib/modules)Default directory containing modules (modules.dep)Default name of modules.dep [*] insmod [*] rmmod [*] lsmod [*] modprobe -----options common to multiple modutils [ ] support version 2.2/2.4 Linux kernels [*]Support tainted module checking with new kernels [*]Support for module .aliases file [*] support for modules.symbols file (3)、在 busybox 中配置对 dev 下设备类型的支持 dev 的创建有三种方法:

手动创建:在制作根文件系统的时候,就在 dev 目录下创建好要使用的设备文件,系统

挂接根文件系统后,就可以使用 dev 目录下的设备文件了。 使用 devfs 文件系统:这种方法已经过时,具有不确定的设备映射、没有足够的主/次设

备号、devfs 消耗大量的内存。 udev:它是个用户程序,能根据系统中硬件设备的状态动态的更新设备文件,包括设备

文件的创建、删除等。它的操作相对复杂,但灵活性很高 mdev 是 busybox 自带的一个简化版的 udev,适合于嵌入式的应用埸合。其具有使用简

单的特点。它的作用,就是在系统启动和热插拔或动态加载驱动程序时,自动产生驱动程序所

需的节点文件。在以 busybox 为基础构建嵌入式 linux 的根文件系统时,使用它是 优的选择。

下面的选项将增加对 mdev 的支持。 Linux System Utilities ---> [*]Support /etc/mdev.conf [*]Support command execution at device addition/removal 4、编译 busybox 编译 busybox 到指定目录: cd /opt/studyarm/busybox-1.13.3 make CONFIG_PREFIX=/opt/studyarm/rootfs install 在 rootfs 目录下会生成目录 bin、sbin、usr 和文件 linuxrc 的内容。

4.2.6 建立 etc 目录下的配置文件

1、etc/mdev.conf 文件,内容为空。 2、拷贝主机 etc 目录下的 passwd、group、shadow 文件到 rootfs/etc 目录下。 3、etc/sysconfig 目录下新建文件 HOSTNAME,内容为”H3-Studio”。 4、etc/inittab 文件: #etc/inittab ::sysinit:/etc/init.d/rcS s3c2410_serial0::askfirst:-/bin/sh ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a –r 5、etc/init.d/rcS 文件: #!/bin/sh PATH=/sbin:/bin:/usr/sbin:/usr/bin runlevel=S prevlevel=N umask 022 export PATH runlevel prevlevel echo "----------munt all----------------" mount -a

echo /sbin/mdev>/proc/sys/kernel/hotplug mdev -s echo "***********************************************" echo "****************Studying ARM*********************" echo "Kernel version:linux-2.6.29.1" echo "Student:Huang huahai" echo "Date:2009.10.1" echo "***********************************************" /bin/hostname -F /etc/sysconfig/HOSTNAME (或者直接 /bin/hostname H3-Studio ) 使用以下命令改变 rcS 的执行权限: Chmod +x rcS 6、etc/fstab 文件: #device mount-point type option dump fsck order proc /proc proc defaults 0 0 none /tmp ramfs defaults 0 0 sysfs /sys sysfs defaults 0 0 mdev /dev ramfs defaults 0 0 7、 etc/profile 文件: #Ash profile #vim:syntax=sh #No core file by defaults #ulimit -S -c 0>/dev/null 2>&1 USER="id -un" LOGNAME=$USER PS1='[\u@\h=W]#' PATH=$PATH HOSTNAME='/bin/hostname' export USER LOGNAME PS1 PATH

4.2.7 制作根文件系统映像文件

使用以下命令安装好 yaffs 文件系统制作工具: cd /mnt/hgfs/share tar –zxvf mkyaffs2image.tgz –C / 在/opt/studyarm 目录下,使用命令 mkyaffs2image rootfs rootfs.img 生成根文件系统映像

文件。 启动信息:

Copy linux kernel from 0x00050000 to 0x30008000, size = 0x00200000 ... done

zImage magic = 0x016f2818

Setup linux parameters at 0x30000100

linux command line is: "noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0"

MACH_TYPE = 782

NOW, Booting Linux......

Uncompressing Linux........................................................................................................................... done, booting the kernel.

Linux version 2.6.29.1 (root@FriendlyARM) (gcc version 4.3.2 (Sourcery G++ Lite 2008q3-72) ) #2 Thu Oct 1 16:46:24 JST

2009

CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177

CPU: VIVT data cache, VIVT instruction cache

Machine: Study-S3C2440

ATAG_INITRD is deprecated; please update your bootloader.

Memory policy: ECC disabled, Data cache writeback

CPU S3C2440A (id 0x32440001)

S3C24XX Clocks, (c) 2004 Simtec Electronics

S3C244X: core 405.000 MHz, memory 101.250 MHz, peripheral 50.625 MHz

CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on

Built 1 zonelists in Zone order, mobility grouping on. Total pages: 16256

Kernel command line: noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0

irq: clearing subpending status 00000003

irq: clearing subpending status 00000002

PID hash table entries: 256 (order: 8, 1024 bytes)

Console: colour dummy device 80x30

console [ttySAC0] enabled

Dentry cache hash table entries: 8192 (order: 3, 32768 bytes)

Inode-cache hash table entries: 4096 (order: 2, 16384 bytes)

Memory: 64MB = 64MB total

Memory: 60828KB available (3556K code, 302K data, 156K init)

Calibrating delay loop... 201.93 BogoMIPS (lpj=504832)

Mount-cache hash table entries: 512

CPU: Testing write buffer coherency: ok

net_namespace: 716 bytes

NET: Registered protocol family 16

S3C2410 Power Management, (c) 2004 Simtec Electronics

S3C2440: Initialising architecture

S3C2440: IRQ Support

S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics

DMA channel 0 at c4808000, irq 33

DMA channel 1 at c4808040, irq 34

DMA channel 2 at c4808080, irq 35

DMA channel 3 at c48080c0, irq 36

S3C244X: Clock Support, DVS off

bio: create slab <bio-0> at 0

SCSI subsystem initialized

usbcore: registered new interface driver usbfs

usbcore: registered new interface driver hub

usbcore: registered new device driver usb

NET: Registered protocol family 2

IP route cache hash table entries: 1024 (order: 0, 4096 bytes)

TCP established hash table entries: 2048 (order: 2, 16384 bytes)

TCP bind hash table entries: 2048 (order: 1, 8192 bytes)

TCP: Hash tables configured (established 2048 bind 2048)

TCP reno registered

NET: Registered protocol family 1

NetWinder Floating Point Emulator V0.97 (extended precision)

JFFS2 version 2.2. (NAND) (SUMMARY) © 2001-2006 Red Hat, Inc.

yaffs Oct 1 2009 16:45:43 Installing.

msgmni has been set to 118

io scheduler noop registered

io scheduler anticipatory registered (default)

io scheduler deadline registered

io scheduler cfq registered

Console: switching to colour frame buffer device 30x40

fb0: s3c2410fb frame buffer device

lp: driver loaded but no devices found

ppdev: user-space parallel port driver

Serial: 8250/16550 driver, 4 ports, IRQ sharing enabled

s3c2440-uart.0: s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2440

s3c2440-uart.1: s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2440

s3c2440-uart.2: s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2440

brd: module loaded

loop: module loaded

dm9000 Ethernet Driver, V1.31

Uniform Multi-Platform E-IDE driver

ide-gd driver 1.18

ide-cd driver 5.00

Driver 'sd' needs updating - please use bus_type methods

S3C24XX NAND Driver, (c) 2004 Simtec Electronics

s3c2440-nand s3c2440-nand: Tacls=1, 9ns Twrph0=4 39ns, Twrph1=1 9ns

NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bit)

Scanning device for bad blocks

Bad eraseblock 3339 at 0x00000342c000

Creating 8 MTD partitions on "NAND 64MiB 3,3V 8-bit":

0x000000000000-0x000000030000 : "bootloder"

0x000000050000-0x000000250000 : "kernel"

0x000000250000-0x000003ffc000 : "root"

0x000000800000-0x000000a00000 : "S3C2410 flash partition 3"

0x000000a00000-0x000000e00000 : "S3C2410 flash partition 4"

0x000000e00000-0x000001800000 : "S3C2410 flash partition 5"

0x000001800000-0x000003000000 : "S3C2410 flash partition 6"

0x000003000000-0x000004000000 : "S3C2410 flash partition 7"

usbmon: debugfs is not available

ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver

s3c2410-ohci s3c2410-ohci: S3C24XX OHCI

s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1

s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000

usb usb1: configuration #1 chosen from 1 choice

hub 1-0:1.0: USB hub found

hub 1-0:1.0: 2 ports detected

usbcore: registered new interface driver libusual

usbcore: registered new interface driver usbserial

USB Serial support registered for generic

usbcore: registered new interface driver usbserial_generic

usbserial: USB Serial Driver core

USB Serial support registered for FTDI USB Serial Device

usbcore: registered new interface driver ftdi_sio

ftdi_sio: v1.4.3:USB FTDI Serial Converters Driver

USB Serial support registered for pl2303

usbcore: registered new interface driver pl2303

pl2303: Prolific PL2303 USB to serial adaptor driver

mice: PS/2 mouse device common for all mice

S3C24XX RTC, (c) 2004,2006 Simtec Electronics

s3c2440-i2c s3c2440-i2c: slave address 0x10

s3c2440-i2c s3c2440-i2c: bus frequency set to 98 KHz

s3c2440-i2c s3c2440-i2c: i2c-0: S3C I2C adapter

S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics

s3c2410-wdt s3c2410-wdt: watchdog inactive, reset disabled, irq enabled

TCP cubic registered

NET: Registered protocol family 17

RPC: Registered udp transport module.

RPC: Registered tcp transport module.

drivers/rtc/hctosys.c: unable to open rtc device (rtc0)

yaffs: dev is 32505858 name is "mtdblock2"

yaffs: passed flags ""

yaffs: Attempting MTD mount on 31.2, "mtdblock2"

yaffs: block 3191 is marked bad

block 3192 is bad

yaffs_read_super: isCheckpointed 0

VFS: Mounted root (yaffs filesystem) on device 31:2.

Freeing init memory: 156K

------mount all------

************************************

**********studyARM******************

Kernel version:linux-2.6.29.1

Student:Huang huahai

Data:2009.10.1

***********************************

Please press Enter to activate this console.

看到了,ok!

4.3 mdev 的使用方法和原理

说明:本文同样出自网友 hugerat 原文网址:http://blog.csdn.net/hugerat/archive/2008/12/03/3437099.aspx mdev 是 busybox 自带的一个简化版的 udev,适合于嵌入式的应用埸合。其具有使用简

单的特点。它的作用,就是在系统启动和热插拔或动态加载驱动程序时,自动产生驱动程序所

需的节点文件。在以 busybox 为基础构建嵌入式 linux 的根文件系统时,使用它是 优的选择。

4.3.1 mdev 的使用

mdev 的使用在 busybox 中的 mdev.txt 文档已经将得很详细了。但作为例子,我简单讲

讲我的使用过程:

(1)在编译时加上对 mdev 的支持 说明: 我使用的是 busybox1.10.1 Linux System Utilities ---> mdev Support /etc/mdev.conf Support command execution at device addition/removal (2)在启动时加上使用 mdev 的命令 我在自己创建的根文件系统(nfs)中的/linuxrc 文件中添加了如下指令: #挂载/sys 为 sysfs 文件系统 echo "----------mount /sys as sysfs" /bin/mount -t tmpfs mdev /dev /bin/mount -t sysfs sysfs /sys echo "----------Starting mdev......" /bin/echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s 注意:是/bin/echo /sbin/mdev > /proc/sys/kernel/hotplug,并非/bin/echo /bin/mdev >

/proc/sys/kernel/hotplug。 busybox 的文档有错!! (3)在驱动中加上对类设备接口的支持

在驱动程序的初始化函数中,使用下述的类似语句,就能在类设备目录下添加包含

设备号的名为“dev”的属性文件。并通过 mdev 在/dev 目录下产生 gpio_dev0 的设备节点文件。 my_class = class_create(THIS_MODULE, "gpio_class"); if(IS_ERR(my_class)) { printk("Err: failed in creating class.\n"); return -1; } /* register your own device in sysfs, and this will cause mdev to create corresponding

device node */ class_device_create(my_class, MKDEV(gpio_major_number, 0), NULL,

"gpio_dev%d" ,0); 在驱动程序的清除程序段,加入以下语句,以完成清除工作。 class_device_destroy(my_class, MKDEV(gpio_major_number, 0));

class_destroy(my_class); 需要的头文件是 linux/device.h,因此程序的开始应加入下句 #include <linux/device.h> 另外,my_class 是 class 类型的结构体指针,要在程序开始时声明成全局变量。 struct class *my_class; 上述程序中的 gpio_major_number 是设备的主节点号。可以换成需要的节点号。

gpio_dev 是 终生成的设备节点文件的名子。%d 是用于以相同设备自动编号的。gpio_class是建立的 class 的名称,当驱动程序加载后,可以在/sys/class 的目录下看到它。

上述语句也不一定要在初始化和清除阶段使用,可以根据需要在其它地方使用。 (4)/etc/mdev.conf 文件 至于/etc/mdev.conf 文件,可有可无,不影响使用,只是添加了些功能。 关于 mdev 的使用方法,我在网上找到一篇中文版的。大家可以到我上传的资源中下

载。

4.3.2 mdev 的原理

要想真正用好 mdev,适当知道一下原理是必不可少的,现在简单介绍一下 mdev 的

原理: (1)执行 mdev -s 说明:以‘-s’为参数调用位于/sbin 目录写的 mdev(其实是个链接,作用是传递参

数给/bin 目录下的 busybox 程序并调用它),mdev 扫描 /sys/class 和/sys/block 中所有的类设备

目录,如果在目录中含有名为“dev”的文件,且文件中包含的是设备号,则 mdev 就利用这

些信息为这个设备在/dev 下创建设备节点文件。一般只在启动时才执行一次 “mdev -s”。 (2)热插拔事件 由于启动时运行了命令:echo /sbin/mdev > /proc/sys/kernel/hotplug ,那么当有热插拔

事件产生时,内核就会调用位于/sbin 目录的 mdev。这时 mdev 通过环境变量中的 ACTION 和 DEVPATH,(这两个变量是系统自带的)来确定此次热插拔事件的动作以及影响了

/sys 中的那个目录。接着会看看这个目录中是否有“dev”的属性文件,如果有就利用这些信

息为 这个设备在/dev 下创建设备节点文件。

4.3.3 一个使用 mdev 的 gpio 控制驱动示例

后,附上我在工作中编写的一段简单的 gpio 控制驱动程序。此程序没有什么功能,

主要是做一些测试用的。有兴趣的朋友可以用它测试一下上述的 mdev 的使用方法。我用的是

友善公司的 mini2440 开发板。 #include <linux/config.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* everything... */ #include <linux/cdev.h> #include <linux/interrupt.h> /* request_irq() */ #include <asm/arch/regs-gpio.h> #include <asm/arch/regs-irq.h> #include <asm/io.h> #include <asm/uaccess.h> /* copy_to_user() */ #include <linux/delay.h> /* mdelay() */ #include <linux/device.h> /*class_create()*/ #include <asm/arch-s3c2410/regs-gpio.h> #include <asm/arch-s3c2410/regs-timer.h> #define VERSION_STRING "gpio driver for JM_Xcontrol" #define DEVICE_NAME "JM_Xcontrol_gpio" /* Use 0xE0 as magic number */ #define XRAY_IOC_MAGIC 0xE0 #define XRAY_IOCLCDBACKLIGHT _IO(XRAY_IOC_MAGIC, 0) #define XRAY_IOC485REC _IO(XRAY_IOC_MAGIC, 1) #define XRAY_IOC485TRC _IO(XRAY_IOC_MAGIC, 2) #define XRAY_IOCBUZZER _IO(XRAY_IOC_MAGIC, 3) #define XRAY_IOC_MAXNR 12 MODULE_AUTHOR("hugerat"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION(VERSION_STRING); unsigned int gpio_major_number=0; struct cdev gpio_dev; struct class *my_class;

static int gpio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int err=0; unsigned long tmp; //-------以下检查命令---------// if (_IOC_TYPE(cmd) != XRAY_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > XRAY_IOC_MAXNR) return -ENOTTY; if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; //--------------------------// switch ( cmd ) { case XRAY_IOCLCDBACKLIGHT: //控制 LCD 背光开关 if(arg==0) { s3c2410_gpio_setpin(S3C2410_GPB1, 1); } else { s3c2410_gpio_setpin(S3C2410_GPB1, 0); } break; case XRAY_IOC485REC: if(arg==0) { s3c2410_gpio_setpin(S3C2410_GPG10, 1); } else { s3c2410_gpio_setpin(S3C2410_GPG10, 0); } break;

case XRAY_IOC485TRC: if(arg==0) { s3c2410_gpio_setpin(S3C2410_GPG12, 0); } else { s3c2410_gpio_setpin(S3C2410_GPG12, 1); } break; case XRAY_IOCBUZZER: if(arg==0) { s3c2410_gpio_setpin(S3C2410_GPB0, 0); } else { s3c2410_gpio_setpin(S3C2410_GPB0, 1); } break; default: break; } return 0; } static struct file_operations gpio_fops = { .owner = THIS_MODULE, //.open = xray_open, //.release = xray_release, //.read = xray_read, //.write = xray_write, .ioctl = gpio_ioctl, //.fasync = xray_fasync, }; static int __init gpio_init(void) { int ret,devno;

dev_t dev; unsigned long tmp; ret = alloc_chrdev_region(&dev,0,1,DEVICE_NAME); gpio_major_number = MAJOR(dev); printk(KERN_INFO "Initial jm_xcontrol_gpio driver!\n"); if (ret<0) { printk(KERN_WARNING "gpio:can't get major number %d\n",gpio_major_number); return ret; } devno = MKDEV(gpio_major_number,0); cdev_init(&gpio_dev,&gpio_fops); gpio_dev.owner = THIS_MODULE; gpio_dev.ops = &gpio_fops; ret = cdev_add(&gpio_dev,devno,1); if (ret) { unregister_chrdev_region(dev,1); printk(KERN_NOTICE "Error %d adding gpio device\n",ret); return ret; } my_class = class_create(THIS_MODULE, "gpio_class"); if(IS_ERR(my_class)) { printk("Err: failed in creating class.\n"); return -1; } /* register your own device in sysfs, and this will cause mdev to create corresponding device node */ class_device_create(my_class, MKDEV(gpio_major_number, 0), NULL, "gpio_dev%d" ,0); //LCD 背光 s3c2410_gpio_cfgpin(S3C2410_GPB1, S3C2410_GPB1_OUTP); s3c2410_gpio_setpin(S3C2410_GPB1, 0); //蜂鸣器 s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP); s3c2410_gpio_setpin(S3C2410_GPB0, 0); //485 收发控制 s3c2410_gpio_cfgpin(S3C2410_GPG10, S3C2410_GPG10_OUTP); //收 s3c2410_gpio_setpin(S3C2410_GPG10, 0);

s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPG12_OUTP); //发 s3c2410_gpio_setpin(S3C2410_GPG12, 0); return 0; } static void __exit gpio_cleanup(void) { unsigned long tmp; dev_t dev=MKDEV(gpio_major_number,0); cdev_del(&gpio_dev); class_device_destroy(my_class, MKDEV(gpio_major_number, 0)); class_destroy(my_class); unregister_chrdev_region(dev,1); s3c2410_gpio_setpin(S3C2410_GPB1, 1); //关背光 s3c2410_gpio_setpin(S3C2410_GPB0, 0); //关蜂鸣器 s3c2410_gpio_setpin(S3C2410_GPG10, 1); //关 485 收 s3c2410_gpio_setpin(S3C2410_GPG12, 0); //关 485 发 printk(KERN_INFO "unregistered the %s\n",DEVICE_NAME); } module_init(gpio_init); module_exit(gpio_cleanup);

(待续)