您当前的位置: 首页 >  韦东山 linux

第一课:linux设备树的引入与体验(基于linux4.19内核版本)

韦东山 发布时间:2018-11-22 11:30:14 ,浏览量:3

在线课堂:https://www.100ask.net/index(课程观看)
论  坛:http://bbs.100ask.net/(学术答疑)
开 发 板:https://100ask.taobao.com/ (淘宝)
     https://weidongshan.tmall.com/(天猫)
交流群一:QQ群:869222007(鸿蒙开发/Linux/嵌入式/驱动/资料下载)
交流群二:QQ群:536785813(单片机-嵌入式)
公 众 号:百问科技

版本日期作者说明
V12020韦东山技术文档

本套视频面向如下三类学员:

  1. 有Linux驱动开发基础的人, 可以挑感兴趣的章节观看;
  2. 没有Linux驱动开发基础但是愿意学习的人,请按顺序全部观看,我会以比较简单的LED驱动为例讲解;
  3. 完全没有Linux驱动知识,又不想深入学习的人, 比如应用开发人员,不得已要改改驱动, 等全部录完后,我会更新本文档,那时再列出您需要观看的章节。

第01节_字符设备的三种写法

怎么写驱动?

①看原理图:

a.确定引脚;

b.看芯片手册,确定如何操作引脚;

②写驱动程序;

起封装作用;

③写测试程序;

如下原理图,VCC经过一个限流电阻到达LED的一端,再通向芯片的引脚上。

当芯片引脚输出低电平时,电流从高电平流向低电平,LED灯点亮;
当芯片引脚输出高电平时,没有电势差,没有电流流过,LED灯不亮;
从原理图可以看出,控制了芯片引脚,就等于控制了灯。

在Linux里,操作硬件都是统一的接口,比如操作LED灯,需要先open,如果要读取LED状态就调用read,如果要操作LED就调用write函数,也可以通过ioctl去实现。
在驱动里,针对前面应用的每个调用函数,都写一个对应的函数,实现对硬件的操作。

可以看出驱动程序起封装作用,它让应用程序访问硬件变得简单,屏蔽了硬件更加复杂的操作。

如何写驱动程序?

①分配一个file_operations结构体;
②设置:
 a. .open=led_open;把led引脚设置为输出引脚
 b. .read=led_write;根据APP传入的值设置引脚状态

③注册(告诉内核),register_chrdev(主设备号,file_operations,name)
④入口函数
⑤出口函数

在驱动中如何指定LED引脚?

有如下三种方法:
①传统方法:在代码led_drv.c中写死;
②总线设备驱动模型:
 a. 在led_drv.c里分配、注册、入口、出口等
 b. 在led_dev.c里指定引脚
③使用设备树指定引脚
 a. 在led_drv.c里分配、注册、入口、出口等
 b. 在jz2440.dts里指定引脚

可以看到,’’‘无论何种方法,驱动写法的核心不变,差别在于如何指定硬件资源’’’。
对比下三种方法的优缺点。
假设这样一个情况,某公司用同一个芯片做了两款产品,其中一款是TV(电视盒子),使用Pin1作为LED的指示灯控制引脚,其中一款是Cam(监控摄像头),使用Pin2作为LED的指示灯控制引脚。

TV设备Cam设备优缺点
1.传统方法led_drv.c
①分配一个file_operations结构体;
②设置:
 a .open=led_open;设置Pin1为输出引脚
 b .read=led_read;根据APP传入的值设置引脚状态
③注册(告诉内核)
④入口函数
⑤出口函数
led_drv.c

①分配一个file_operations结构体;
②设置:
 a. .open=led_open;设置Pin2为输出引脚
 b. .read=led_read;根据APP传入的值设置引脚状态
③注册(告诉内核)
④入口函数
⑤出口函数
优点:简单

缺点:不易扩展,需要重新编译
2.总线设备驱动模型led_drv.c
①分配/设置/注册 platform_driver;
② .probe:
 a 分配一个file_operations结构体;
b .open=led_open;设置平台设备总指定的引脚为输出引脚
  .read=led_read;根据APP传入的值设置引脚状态
c注册
③ .driver{ .name }

led_dev.c
①分配/设置/注册 platform_device;
② .resource:指定引脚;,name为Pin1

led_dev.c

①分配/设置/注册 platform_driver;
② .resource:指定引脚;,name为Pin2
优点:易扩展

缺点:稍复杂,冗余代码太多,需要重新编译
3.设备树led_drv.c
①分配/设置/注册 platform_driver;
② .probe:
 a 分配一个file_operations结构体;
b .open=led_open;设置平台设备总指定的引脚为输出引脚
  .read=led_read;根据APP传入的值设置引脚状态
c注册
③ .driver{ .name }

.dts指定资源
内核根据dts生成的dtb文件分配/设置/注册platform_device
.dts指定资源

内核根据dts生成的dtb文件分配/设置/注册platform_device
优点:易扩展

缺点:稍复杂,冗余代码太多,需要重新编译

第02节_字符设备驱动的传统写法

在上一节视频里我们介绍了三种编写驱动的方法,也对比了它们的优缺点,后面我们将使用比较快速的方法写出驱动程序,因为写驱动程序不是我们这套视频的重点,所以尽快的把驱动程序写出来,给大家展示一下。

这节视频我们使用传统的方法编写字符驱动程序,以最简单的点灯驱动程序为示例。
先回顾下写字符设备驱动的五个步骤:
1.2.3.分配/设置/注册file_operations
4.入口
5.出口
所谓分配file_operations,我们可以定义一个file_operations结构体,就不需要分配了。

static struct file_operations myled_oprs = {
	.owner = THIS_MODULE, //表示这个模块本身
	.open  = led_open,
	.write = led_write,
	.release = led_release,
};

定义好了file_operations结构体,再去入口函数注册结构体。

static int myled_init(void)
{
	major = register_chrdev(0, "myled", &myled_oprs);

	return 0;
}

第一个参数:主设备号写0,让系统为我们分配;
第二个参数:设置名字,没有特殊要求;
第三个参数:file_operations结构体;
对应的出口操作进行相反向操作:

static void myled_exit(void)
{
	unregister_chrdev(major, "myled");
}

然后用宏module_init对入口、出口函数进行修饰,表示它们和普通函数不一样:

module_init(myled_init);
module_exit(myled_exit);

module_init(myled_init)实际就是int init_module(void) attribute((alias(“myled_init”))),表示myled_init的别名是init_module,以后就可以使用init_module来引用myled_init

此外,还要加上GPL协议:

MODULE_LICENSE("GPL");

写到这里,驱动程序的框架已经搭建起来了,接下来实现具体的硬件操作函数:led_open()和led_write()。
在led_open()里把对应的引脚配置为输出引脚,在led_write()根据应用程序传入的数据点灯,让其输出高电平或低电平。
为了让程序更具有扩展性,把GPIO的寄存器放在一个数组里:

static unsigned int gpio_base[] = {
	0x56000000, /* GPACON */
	0x56000010, /* GPBCON */
	0x56000020, /* GPCCON */
	0x56000030, /* GPDCON */
	0x56000040, /* GPECON */
	0x56000050, /* GPFCON */
	0x56000060, /* GPGCON */
	0x56000070, /* GPHCON */
	0,          /* GPICON */
	0x560000D0, /* GPJCON */
};

定义好了引脚的组,还得确定使用该组的哪个引脚,使用宏来确定哪个引脚:

#define S3C2440_GPA(n)  (0            
关注
打赏
查看更多评论