1)实验平台:正点原子Linux开发板

2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南

关注官方微信号公众号,获取更多资料:正点原子

上一章我们详细的讲解了设备树语法以及在驱动开发中常用的OF函数,本章我们就开始第一个基于设备树的Linux驱动实验。本章在第四十二章实验的基础上完成,只是将其驱动开发改为设备树形式而已。

44.1 设备树LED驱动原理

在《第四十二章新字符设备驱动实验》中,我们直接在驱动文件newchrled.c中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。本章我们在第四十二章实验基础上完成,本章我们使用设备树来向Linux内核传递相关的寄存器物理地址,Linux驱动文件使用上一章讲解的OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的IO。本章实验还是比较简单的,本章实验重点内容如下:

①、在imx6ull-alientek-emmc.dts文件中创建相应的设备节点。

②、编写驱动程序(在第四十二章实验基础上完成),获取设备树中的相关属性值。

③、使用获取到的有关属性值来初始化LED所使用的GPIO。

44.2硬件原理图分析

本章实验硬件原理图参考8.3小节即可。

44.3实验程序编写

本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->4_dtsled。

本章实验在四十二章实验的基础上完成,重点是将驱动改为基于设备树的.

44.3.1 修改设备树文件

在根节"/"下创建一个名为"alphaled"的子节点,打开imx6ull-alientek-emmc.dts文件,在根节点"/"最后面输入如下所示内容:

示例代码44.3.1.1 alphaled节点

1 alphaled {

2 #address-cells =<1>;

3 #size-cells =<1>;

4 compatible ="atkalpha-led";

5 status ="okay";

6 reg =< 0X020C406C0X04 /* CCM_CCGR1_BASE */

7 0X020E00680X04 /* SW_MUX_GPIO1_IO03_BASE */

8 0X020E02F40X04 /* SW_PAD_GPIO1_IO03_BASE */

9 0X0209C0000X04 /* GPIO1_DR_BASE */

10 0X0209C0040X04>; /* GPIO1_GDIR_BASE */

11};

第2、3行,属性#address-cells和#size-cells都为1,表示reg属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。

第4行,属性compatbile设置alphaled节点兼容性为"atkalpha-led"。

第5行,属性status设置状态为"okay"。

第6~10行,reg属性,非常重要!reg属性设置了驱动里面所要使用的寄存器物理地址,比如第6行的"0X020C406C 0X04"表示I.MX6ULL的CCM_CCGR1寄存器,其中寄存器首地址为0X020C406C,长度为4个字节。

设备树修改完成以后输入如下命令重新编译一下imx6ull-alientek-emmc.dts:

makedtbs

编译完成以后得到imx6ull-alientek-emmc.dtb,使用新的imx6ull-alientek-emmc.dtb启动Linux内核。Linux启动成功以后进入到/proc/device-tree/目录中查看是否有"alphaled"这个节点,结果如图44.3.1.1所示:

图44.3.1.1 alphaled节点

如果没有"alphaled"节点的话请重点下面两点:

①、检查设备树修改是否成功,也就是alphaled节点是否为根节点"/"的子节点。

②、检查是否使用新的设备树启动的Linux内核。

可以进入到图44.3.1中的alphaled目录中,查看一下都有哪些属性文件,结果如图44.3.1.2所示:

图44.3.1.2 alphaled节点文件

大家可以查看一下compatible、status等属性值是否和我们设置的一致。

44.3.2 LED灯驱动程序编写

设备树准备好以后就可以编写驱动程序了,本章实验在第四十二章实验驱动文件newchrled.c的基础上修改而来。新建名为"4_dtsled"文件夹,然后在4_dtsled文件夹里面创建vscode工程,工作区命名为"dtsled"。工程创建好以后新建dtsled.c文件,在dtsled.c里面输入如下内容:

示例代码44.3.2.1 dtsled.c文件内容

1 #include <linux/types.h>

2 #include <linux/kernel.h>

3 #include <linux/delay.h>

4 #include <linux/ide.h>

5 #include <linux/init.h>

6 #include <linux/module.h>

7 #include <linux/errno.h>

8 #include <linux/gpio.h>

9 #include <linux/cdev.h>

10 #include <linux/device.h>

11 #include <linux/of.h>

12 #include <linux/of_address.h>

13 #include <asm/mach/map.h>

14 #include <asm/uaccess.h>

15 #include <asm/io.h>

16/***************************************************************

17 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

18文件名 : dtsled.c

19作者 : 左忠凯

20版本 : V1.0

21描述 : LED驱动文件。

22其他 : 无

23论坛 : www.openedv.com

24日志 : 初版V1.0 2019/7/9 左忠凯创建

25 ***************************************************************/

26 #define DTSLED_CNT 1 /* 设备号个数 */

27 #define DTSLED_NAME "dtsled" /* 名字 */

28 #define LEDOFF 0 /* 关灯 */

29 #define LEDON 1 /* 开灯 */

30

31/* 映射后的寄存器虚拟地址指针 */

32staticvoid __iomem *IMX6U_CCM_CCGR1;

33staticvoid __iomem *SW_MUX_GPIO1_IO03;

34staticvoid __iomem *SW_PAD_GPIO1_IO03;

35staticvoid __iomem *GPIO1_DR;

36staticvoid __iomem *GPIO1_GDIR;

37

38/* dtsled设备结构体 */

39struct dtsled_dev{

40 dev_t devid; /* 设备号 */

41struct cdev cdev; /* cdev */

42struct class *class; /* 类 */

43struct device *device; /* 设备 */

44int major; /* 主设备号 */

45int minor; /* 次设备号 */

46struct device_node *nd;/* 设备节点 */

47};

48

49struct dtsled_dev dtsled;/* led设备 */

50

51/*

52 * @description : LED打开/关闭

53 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED

54 * @return : 无

55 */

56void led_switch(u8 sta)

57{

58 u32 val =0;

59if(sta == LEDON){

60 val = readl(GPIO1_DR);

61 val &=~(1<<3);

62 writel(val, GPIO1_DR);

63}elseif(sta == LEDOFF){

64 val = readl(GPIO1_DR);

65 val|=(1<<3);

66 writel(val, GPIO1_DR);

67}

68}

69

70/*

71 * @description : 打开设备

72 * @param – inode : 传递给驱动的inode

73 * @param – filp : 设备文件,file结构体有个叫做private_data的成员变量

74 * 一般在open的时候将private_data指向设备结构体。

75 * @return : 0 成功;其他失败

76 */

77staticint led_open(struct inode *inode,struct file *filp)

78{

79 filp->private_data =&dtsled;/* 设置私有数据 */

80return0;

81}

82

83/*

84 * @description : 从设备读取数据

85 * @param – filp : 要打开的设备文件(文件描述符)

86 * @param - buf : 返回给用户空间的数据缓冲区

87 * @param - cnt : 要读取的数据长度

88 * @param – offt : 相对于文件首地址的偏移

89 * @return : 读取的字节数,如果为负值,表示读取失败

90 */

91static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt, loff_t *offt)

92{

93return0;

94}

95

96/*

97 * @description : 向设备写数据

98 * @param - filp : 设备文件,表示打开的文件描述符

99 * @param - buf : 要写给设备写入的数据

100 * @param - cnt : 要写入的数据长度

101 * @param – offt : 相对于文件首地址的偏移

102 * @return : 写入的字节数,如果为负值,表示写入失败

103 */

104static ssize_t led_write(struct file *filp,constchar __user *buf,size_t cnt, loff_t *offt)

105{

106int retvalue;

107unsignedchar databuf[1];

108unsignedchar ledstat;

109

110 retvalue = copy_from_user(databuf, buf, cnt);

111if(retvalue <0){

112 printk("kernel write failed!");

113return-EFAULT;

114}

115

116 ledstat = databuf[0]; /* 获取状态值 */

117

118if(ledstat == LEDON){

119 led_switch(LEDON); /* 打开LED灯 */

120}elseif(ledstat == LEDOFF){

121 led_switch(LEDOFF); /* 关闭LED灯 */

122}

123return0;

124}

125

126/*

127 * @description : 关闭/释放设备

128 * @param – filp : 要关闭的设备文件(文件描述符)

129 * @return : 0 成功;其他失败

130 */

131staticint led_release(struct inode *inode,struct file *filp)

132{

133return0;

134}

135

136/* 设备操作函数 */

137staticstruct file_operations dtsled_fops ={

138.owner = THIS_MODULE,

139.open = led_open,

140.read = led_read,

141.write = led_write,

142.release = led_release,

143};

144

145/*

146 * @description : 驱动入口函数

147 * @param : 无

148 * @return : 无

149 */

150staticint __init led_init(void)

151{

152 u32 val =0;

153int ret;

154 u32 regdata[14];

155constchar*str;

156struct property *proper;

157

158/* 获取设备树中的属性数据 */

159/* 1、获取设备节点:alphaled */

160 dtsled.nd = of_find_node_by_path("/alphaled");

161if(dtsled.nd ==NULL){

162 printk("alphaled node not find!");

163return-EINVAL;

164}else{

165 printk("alphaled node find!");

166}

167

168/* 2、获取compatible属性内容 */

169 proper = of_find_property(dtsled.nd,"compatible",NULL);

170if(proper ==NULL){

171 printk("compatible property find failed");

172}else{

173 printk("compatible = %s",(char*)proper->value);

174}

175

176/* 3、获取status属性内容 */

177 ret = of_property_read_string(dtsled.nd,"status",&str);

178if(ret <0){

179 printk("status read failed!");

180}else{

181 printk("status = %s",str);

182}

183

184/* 4、获取reg属性内容 */

185 ret = of_property_read_u32_array(dtsled.nd,"reg", regdata,10);

186if(ret <0){

187 printk("reg property read failed!");

188}else{

189 u8 i =0;

190 printk("reg data:");

191for(i =0; i <10; i++)

192 printk("%#X ", regdata[i]);

193 printk("");

194}

195

196/* 初始化LED */

197 #if0

198/* 1、寄存器地址映射 */

199 IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);

200 SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);

201 SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);

202 GPIO1_DR = ioremap(regdata[6], regdata[7]);

203 GPIO1_GDIR = ioremap(regdata[8], regdata[9]);

204 #else

205 IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd,0);

206 SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd,1);

207 SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd,2);

208 GPIO1_DR = of_iomap(dtsled.nd,3);

209 GPIO1_GDIR = of_iomap(dtsled.nd,4);

210 #endif

211

212/* 2、使能GPIO1时钟 */

213 val = readl(IMX6U_CCM_CCGR1);

214 val &=~(3<<26);/* 清楚以前的设置 */

215 val |=(3<<26);/* 设置新值 */

216 writel(val, IMX6U_CCM_CCGR1);

217

218/* 3、设置GPIO1_IO03的复用功能,将其复用为

219 * GPIO1_IO03,最后设置IO属性。

220 */

221 writel(5, SW_MUX_GPIO1_IO03);

222

223/* 寄存器SW_PAD_GPIO1_IO03设置IO属性 */

224 writel(0x10B0, SW_PAD_GPIO1_IO03);

225

226/* 4、设置GPIO1_IO03为输出功能 */

227 val = readl(GPIO1_GDIR);

228 val &=~(1<<3);/* 清除以前的设置 */

229 val |=(1<<3);/* 设置为输出 */

230 writel(val, GPIO1_GDIR);

231

232/* 5、默认关闭LED */

233 val = readl(GPIO1_DR);

234 val |=(1<<3);

235 writel(val, GPIO1_DR);

236

237/* 注册字符设备驱动 */

238/* 1、创建设备号 */

239if(dtsled.major){ /* 定义了设备号 */

240 dtsled.devid = MKDEV(dtsled.major,0);

241 register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);

242}else{/* 没有定义设备号 */

243 alloc_chrdev_region(&dtsled.devid,0, DTSLED_CNT, DTSLED_NAME);/* 申请设备号 */

244 dtsled.major = MAJOR(dtsled.devid);/* 获取分配号的主设备号 */

245 dtsled.minor = MINOR(dtsled.devid);/* 获取分配号的次设备号 */

246}

247 printk("dtsled major=%d,minor=%d",dtsled.major, dtsled.minor);

248

249/* 2、初始化cdev */

250 dtsled.cdev.owner = THIS_MODULE;

251 cdev_init(&dtsled.cdev,&dtsled_fops);

252

253/* 3、添加一个cdev */

254 cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);

255

256/* 4、创建类 */

257 dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);

258if(IS_ERR(dtsled.class)){

259return PTR_ERR(dtsled.class);

260}

261

262/* 5、创建设备 */

263 dtsled.device = device_create(dtsled.class,NULL, dtsled.devid,NULL, DTSLED_NAME);

264if(IS_ERR(dtsled.device)){

265return PTR_ERR(dtsled.device);

266}

267

268return0;

269}

270

271/*

272 * @description : 驱动出口函数

273 * @param : 无

274 * @return : 无

275 */

276staticvoid __exit led_exit(void)

277{

278/* 取消映射 */

279 iounmap(IMX6U_CCM_CCGR1);

280 iounmap(SW_MUX_GPIO1_IO03);

281 iounmap(SW_PAD_GPIO1_IO03);

282 iounmap(GPIO1_DR);

283 iounmap(GPIO1_GDIR);

284

285/* 注销字符设备驱动 */

286 cdev_del(&dtsled.cdev);/* 删除cdev */

287 unregister_chrdev_region(dtsled.devid, DTSLED_CNT);/*注销设备号*/

288

289 device_destroy(dtsled.class, dtsled.devid);

290 class_destroy(dtsled.class);

291}

292

293 module_init(led_init);

294 module_exit(led_exit);

295 MODULE_LICENSE("GPL");

296 MODULE_AUTHOR("zuozhongkai");

dtsled.c文件中的内容和第四十二章的newchrled.c文件中的内容基本一样,只是dtsled.c中包含了处理设备树的代码,我们重点来看一下这部分代码。

第46行,在设备结构体dtsled_dev中添加了成员变量nd,nd是device_node结构体类型指针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加device_node指针变量来存放这个节点。

第160~166行,通过of_find_node_by_path函数得到alphaled节点,后续其他的OF函数要使用device_node。

第169~174行,通过of_find_property函数获取alphaled节点的compatible属性,返回值为property结构体类型指针变量,property的成员变量value表示属性值。

第177~182行,通过of_property_read_string函数获取alphaled节点的status属性值。

第185~194行,通过of_property_read_u32_array函数获取alphaled节点的reg属性所有值,并且将获取到的值都存放到regdata数组中。第192行将获取到的reg属性值依次输出到终端上。

第199~203行,使用"古老"的ioremap函数完成内存映射,将获取到的regdata数组中的寄存器物理地址转换为虚拟地址。

第205~209行,使用of_iomap函数一次性完成读取reg属性以及内存映射,of_iomap函数是设备树推荐使用的OF函数。

44.3.3 编写测试APP

本章直接使用第四十二章的测试APP,将上一章的ledApp.c文件复制到本章实验工程下即可。

44.4 运行测试

44.4.1 编译驱动程序和测试APP

1、编译驱动程序

编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为dtsled.o,Makefile内容如下所示:

示例代码44.4.1.1 Makefile文件

1 KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

......

4 obj-m := dtsled.o.o

......

11 clean:

12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4行,设置obj-m变量的值为dtsled.o。

输入如下命令编译出驱动模块文件:

make-j32

编译成功以后就会生成一个名为"dtsled.ko"的驱动模块文件。

2、编译测试APP

输入如下命令编译测试ledApp.c这个测试程序:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

编译成功以后就会生成ledApp这个应用程序。

44.4.2 运行测试

将上一小节编译出来的dtsled.ko和ledApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载dtsled.ko驱动模块:

depmod //第一次加载驱动的时候需要运行此命令

modprobe dtsled.ko //加载驱动

驱动加载成功以后会在终端中输出一些信息,如图44.4.2.1所示:

图44.4.2.1 驱动加载成功以后输出的信息

从图44.4.2.1可以看出,alpahled这个节点找到了,并且compatible属性值为"atkalpha-led",status属性值为"okay",reg属性的值为"0X20C406C 0X4 0X20E0068 0X4 0X20E02F4 0X4 0X209C000 0X4 0X209C004 0X4",这些都和我们设置的设备树一致。

驱动加载成功以后就可以使用ledApp软件来测试驱动是否工作正常,输入如下命令打开LED灯:

./ledApp /dev/dtsled 1 //打开LED灯

输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭LED灯:

./ledApp /dev/dtsled 0 //关闭LED灯

输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否熄灭。如果要卸载驱动的话输入如下命令即可:

rmmoddtsled.ko

韦东山 IMX6ULL和正点原子_「正点原子Linux连载」第四十四章设备树下的LED驱动实验...相关推荐

  1. 【正点原子Linux连载】第四十四章 设备树下的LED驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  2. 【正点原子MP157连载】第二十四章 设备树下的LED驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  3. 【正点原子MP157连载】第三十五章 设备树下的platform驱动编写-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  4. linux i2c adapter 增加设备_「正点原子Linux连载」第六十二章Linux SPI驱动实验(一)...

    1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南>关注官方微信号公众号,获取更多资料:正点原子 第六十二章Linux SPI驱动实验 上一 ...

  5. 嵌入式实时操作系统ucos-ii_「正点原子NANO STM32开发板资料连载」第三十六章 UCOSII 实验 1任务调度...

    1)实验平台:alientek NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第三十六章 ...

  6. Linux 设备树下的 platform 驱动实验基于正点原子IMX6ULL开发板

    1 设备树下的 platform 驱动简介 platform 驱动框架分为总线.设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是 Linux 内核提供的,我们在编写驱动的时候只要关注于设备和 ...

  7. 韦东山 IMX6ULL和正点原子_「正点原子Linux连载」第四十三章Linux设备树(一)

    1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南> 关注官方微信号公众号,获取更多资料:正点原子 前面章节中我们多次提到"设备树 ...

  8. 【正点原子Linux连载】第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  9. 【正点原子Linux连载】第六十二章 Linux SPI驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

最新文章

  1. 博客园的博客积分与排名查看方法
  2. python3字符串的常见操作
  3. ubuntu linux root,Ubuntu 中的 root 用户:你应该知道的重要事情 | Linux 中国
  4. 面对疫情等群体性危机,程序员如何在家高效办公?
  5. 软件工程——HelloWorld
  6. 【干货分享】硬件测试工程师必备基本技能,看这一篇就够!
  7. 石河子市谷歌高清卫星地图下载
  8. Windows Subsystem for Android (WSA) 下载:在 Windows 11 上运行 Android 应用
  9. 【云原生】-Docker快速搭建免费的WordPress博客平台
  10. Git 基本操作(入职亲体验)
  11. APIO2014 连珠线
  12. 「备战春招/秋招系列」程序员的简历就该这样写...
  13. 搜索引擎如何判定站群是否作弊?
  14. 计算机考研数学书,计算机考研参考书(专业课、数学、英语)
  15. 二分类最优阈值确定_一文搞懂分类算法中常用的评估指标
  16. 从因果关系来看小样本学习
  17. bzoj5020[THUWC 2017]在美妙的数学王国中畅游(LCT)
  18. 如何压缩打包图片文件?照片如何打包压缩?
  19. 走了....我不是徐志摩,我不知道怎样轻轻的走....
  20. 处理机调度算法模拟实验——FCFS算法,SJF调度算法

热门文章

  1. 360行车记录仪起死回生
  2. 这款开源工具,帮你轻松切换 Xcode 版本
  3. 龙白滔:一个实用的中国央行数字货币和Libra设计方案
  4. 基于uniapp+express+mysql购物商城模板设计
  5. HTML5期末大作业:生活类购物商城网站设计——生活类购物商城模板(2页)
  6. AppScan安全问题解决方案
  7. #使用Python的turtle绘制正六边形、叠边形
  8. 用python画六边形并填充颜色_如何用Python,画一个正多边形,长度和颜色还是任意的!...
  9. ANSYS Workbench LS-DYNA显式结构动力学专题视频教程
  10. CMD修改计算机睡眠、休眠时间