• Linux学习第16天:Linux设备树下的LED驱动开发:举一反三 专注专心专业


    Linux版本号4.1.15   芯片I.MX6ULL                                     大叔学Linux    品人间百味  思文短情长


             在开题之前,先说一下这次的题目,尤其是后面的“举一反三 专注专心专业”到底想给大家传递什么信息。LED驱动开发,目前为止已经学了好几种方法,包括裸机开发、嵌入式Linux LED驱动开发以及基于API函数的LED驱动开发,再加上今天要学习的基于Linux设备树的LED驱动开发,已经整整学了4种。原因也很简单,除了LED驱动开发是最基础最简单的驱动开发,通过不同方式实现其驱动开发,更便于分析各种方法之间的异同点,更容易消化理解刚学的内容。从这一点来说,举一反三,是也。而专注专心专业三者贵在一个专字。世间知识千千万,学习嵌入式Linux驱动开发,万千江湖,只取一瓢。在当今尤其是社会分工愈来愈细化的今天,保持一个专字,在自己选定的领域保持专注专心和专业,才能使自己变的更强、眼光放的更远,思考的深度更深。

            今天的这篇笔记要学习的内容是基于Linux设备树的LED驱动开发,主要内容包括原理、程序编写和测试,重点是程序的编写,其内容又包含设备树文件的修改、驱动程序编写以及测试APP的编写。

            下图为本笔记的思维导图:

    一、设备树LED驱动原理

            使用设备树来向 Linux 内核传递相关的寄存器物理地址, Linux 驱动文件使用上一章讲解的 OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的 IO。

    二、实验程序编写

    1.修改设备树文件

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

    1. 1 alphaled {
    2. 2 #address-cells = <1>;
    3. 3 #size-cells = <1>;
    4. 4 compatible = "atkalpha-led";
    5. 5 status = "okay";
    6. 6 reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
    7. 7 0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
    8. 8 0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
    9. 9 0X0209C000 0X04 /* GPIO1_DR_BASE */
    10. 10 0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
    11. 11 };

            特别关注一下第6-10行,reg 属性设置了驱动里面所要使用的寄存器物理地址。

            输入如下命令,重新编译一下imx6ull-alientek-emmc.dts。

    make dtbs

            使用编译后的新的imx6ull-alientek-emmc.dtb启动Linux内核。启动成功后,在/proc/device-tree目录下会看到alphaled这个节点,如下:




    2.LED驱动程序编写

            直接上代码吧,如下:

    1. 1 #include
    2. 2 #include
    3. 3 #include
    4. 4 #include
    5. 5 #include
    6. 6 #include
    7. 7 #include
    8. 8 #include
    9. 9 #include
    10. 10 #include
    11. 11 #include
    12. 12 #include
    13. 13 #include
    14. 14 #include
    15. 15 #include
    16. 16 /***************************************************************
    17. 17 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
    18. 18 文件名 : dtsled.c
    19. 19 作者 : 左忠凯
    20. 20 版本 : V1.0
    21. 21 描述 : LED 驱动文件。
    22. 22 其他 : 无
    23. 23 论坛 : www.openedv.com
    24. 24 日志 : 初版 V1.0 2019/7/9 左忠凯创建
    25. 25 ***************************************************************/
    26. 26 #define DTSLED_CNT 1 /* 设备号个数 */
    27. 27 #define DTSLED_NAME "dtsled" /* 名字 */
    28. 28 #define LEDOFF 0 /* 关灯 */
    29. 29 #define LEDON 1 /* 开灯 */
    30. 30
    31. 31 /* 映射后的寄存器虚拟地址指针 */
    32. 32 static void __iomem *IMX6U_CCM_CCGR1;
    33. 33 static void __iomem *SW_MUX_GPIO1_IO03;
    34. 34 static void __iomem *SW_PAD_GPIO1_IO03;
    35. 35 static void __iomem *GPIO1_DR;
    36. 36 static void __iomem *GPIO1_GDIR;
    37. 37
    38. 38 /* dtsled 设备结构体 */
    39. 39 struct dtsled_dev{
    40. 40 dev_t devid; /* 设备号 */
    41. 41 struct cdev cdev; /* cdev */
    42. 42 struct class *class; /* 类 */
    43. 43 struct device *device; /* 设备 */
    44. 44 int major; /* 主设备号 */
    45. 45 int minor; /* 次设备号 */
    46. 46 struct device_node *nd; /* 设备节点 */
    47. 47 };
    48. 48
    49. 49 struct dtsled_dev dtsled; /* led 设备 */
    50. 50
    51. 51 /*
    52. 52 * @description : LED 打开/关闭
    53. 53 * @param - sta : LEDON(0) 打开 LED, LEDOFF(1) 关闭 LED
    54. 54 * @return : 无
    55. 55 */
    56. 56 void led_switch(u8 sta)
    57. 57 {
    58. 58 u32 val = 0;
    59. 59 if(sta == LEDON) {
    60. 60 val = readl(GPIO1_DR);
    61. 61 val &= ~(1 << 3);
    62. 62 writel(val, GPIO1_DR);
    63. 63 }else if(sta == LEDOFF) {
    64. 64 val = readl(GPIO1_DR);
    65. 65 val|= (1 << 3);
    66. 66 writel(val, GPIO1_DR);
    67. 67 }
    68. 68 }
    69. 69
    70. 70 /*
    71. 71 * @description : 打开设备
    72. 72 * @param – inode : 传递给驱动的 inode
    73. 73 * @param – filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
    74. 74 * 一般在 open 的时候将 private_data 指向设备结构体。
    75. 75 * @return : 0 成功;其他 失败
    76. 76 */
    77. 77 static int led_open(struct inode *inode, struct file *filp)
    78. 78 {
    79. 79 filp->private_data = &dtsled; /* 设置私有数据 */
    80. 80 return 0;
    81. 81 }
    82. 82
    83. 83 /*
    84. 84 * @description : 从设备读取数据
    85. 85 * @param – filp : 要打开的设备文件(文件描述符)
    86. 86 * @param - buf : 返回给用户空间的数据缓冲区
    87. 87 * @param - cnt : 要读取的数据长度
    88. 88 * @param – offt : 相对于文件首地址的偏移
    89. 89 * @return : 读取的字节数,如果为负值,表示读取失败
    90. 90 */
    91. 91 static ssize_t led_read(struct file *filp, char __user *buf,
    92. size_t cnt, loff_t *offt)
    93. 92 {
    94. 93 return 0;
    95. 94 }
    96. 95
    97. 96 /*
    98. 97 * @description : 向设备写数据
    99. 98 * @param - filp : 设备文件,表示打开的文件描述符
    100. 99 * @param - buf : 要写给设备写入的数据
    101. 100 * @param - cnt : 要写入的数据长度
    102. 101 * @param – offt : 相对于文件首地址的偏移
    103. 102 * @return : 写入的字节数,如果为负值,表示写入失败
    104. 103 */
    105. 104 static ssize_t led_write(struct file *filp, const char __user *buf,
    106. size_t cnt, loff_t *offt)
    107. 105 {
    108. 106 int retvalue;
    109. 107 unsigned char databuf[1];
    110. 108 unsigned char ledstat;
    111. 109
    112. 110 retvalue = copy_from_user(databuf, buf, cnt);
    113. 111 if(retvalue < 0) {
    114. 112 printk("kernel write failed!\r\n");
    115. 113 return -EFAULT;
    116. 114 }
    117. 115
    118. 116 ledstat = databuf[0]; /* 获取状态值 */
    119. 117
    120. 118 if(ledstat == LEDON) {
    121. 119 led_switch(LEDON); /* 打开 LED 灯 */
    122. 120 } else if(ledstat == LEDOFF) {
    123. 121 led_switch(LEDOFF); /* 关闭 LED 灯 */
    124. 122 }
    125. 123 return 0;
    126. 124 }
    127. 125
    128. 126 /*
    129. 127 * @description : 关闭/释放设备
    130. 128 * @param – filp : 要关闭的设备文件(文件描述符)
    131. 129 * @return : 0 成功;其他 失败
    132. 130 */
    133. 131 static int led_release(struct inode *inode, struct file *filp)
    134. 132 {
    135. 133 return 0;
    136. 134 }
    137. 135
    138. 136 /* 设备操作函数 */
    139. 137 static struct file_operations dtsled_fops = {
    140. 138 .owner = THIS_MODULE,
    141. 139 .open = led_open,
    142. 140 .read = led_read,
    143. 141 .write = led_write,
    144. 142 .release = led_release,
    145. 143 };
    146. 144
    147. 145 /*
    148. 146 * @description : 驱动入口函数
    149. 147 * @param : 无
    150. 148 * @return : 无
    151. 149 */
    152. 150 static int __init led_init(void)
    153. 151 {
    154. 152 u32 val = 0;
    155. 153 int ret;
    156. 154 u32 regdata[14];
    157. 155 const char *str;
    158. 156 struct property *proper;
    159. 157
    160. 158 /* 获取设备树中的属性数据 */
    161. 159 /* 1、获取设备节点: alphaled */
    162. 160 dtsled.nd = of_find_node_by_path("/alphaled");
    163. 161 if(dtsled.nd == NULL) {
    164. 162 printk("alphaled node can not found!\r\n");
    165. 163 return -EINVAL;
    166. 164 } else {
    167. 165 printk("alphaled node has been found!\r\n");
    168. 166 }
    169. 167
    170. 168 /* 2、获取 compatible 属性内容 */
    171. 169 proper = of_find_property(dtsled.nd, "compatible", NULL);
    172. 170 if(proper == NULL) {
    173. 171 printk("compatible property find failed\r\n");
    174. 172 } else {
    175. 173 printk("compatible = %s\r\n", (char*)proper->value);
    176. 174 }
    177. 175
    178. 176 /* 3、获取 status 属性内容 */
    179. 177 ret = of_property_read_string(dtsled.nd, "status", &str);
    180. 178 if(ret < 0){
    181. 179 printk("status read failed!\r\n");
    182. 180 } else {
    183. 181 printk("status = %s\r\n",str);
    184. 182 }
    185. 183
    186. 184 /* 4、获取 reg 属性内容 */
    187. 185 ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
    188. 186 if(ret < 0) {
    189. 187 printk("reg property read failed!\r\n");
    190. 188 } else {
    191. 189 u8 i = 0;
    192. 190 printk("reg data:\r\n");
    193. 191 for(i = 0; i < 10; i++)
    194. 192 printk("%#X ", regdata[i]);
    195. 193 printk("\r\n");
    196. 194 }
    197. 195
    198. 196 /* 初始化 LED */
    199. 197 #if 0
    200. 198 /* 1、寄存器地址映射 */
    201. 199 IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
    202. 200 SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
    203. 201 SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
    204. 202 GPIO1_DR = ioremap(regdata[6], regdata[7]);
    205. 203 GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
    206. 204 #else
    207. 205 IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
    208. 206 SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
    209. 207 SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
    210. 208 GPIO1_DR = of_iomap(dtsled.nd, 3);
    211. 209 GPIO1_GDIR = of_iomap(dtsled.nd, 4);
    212. 210 #endif
    213. 211
    214. 212 /* 2、使能 GPIO1 时钟 */
    215. 213 val = readl(IMX6U_CCM_CCGR1);
    216. 214 val &= ~(3 << 26); /* 清楚以前的设置 */
    217. 215 val |= (3 << 26); /* 设置新值 */
    218. 216 writel(val, IMX6U_CCM_CCGR1);
    219. 217
    220. 218 /* 3、设置 GPIO1_IO03 的复用功能,将其复用为
    221. 219 * GPIO1_IO03,最后设置 IO 属性。
    222. 220 */
    223. 221 writel(5, SW_MUX_GPIO1_IO03);
    224. 222
    225. 223 /* 寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 */
    226. 224 writel(0x10B0, SW_PAD_GPIO1_IO03);
    227. 225
    228. 226 /* 4、设置 GPIO1_IO03 为输出功能 */
    229. 227 val = readl(GPIO1_GDIR);
    230. 228 val &= ~(1 << 3); /* 清除以前的设置 */
    231. 229 val |= (1 << 3); /* 设置为输出 */
    232. 230 writel(val, GPIO1_GDIR);
    233. 231
    234. 232 /* 5、默认关闭 LED */
    235. 233 val = readl(GPIO1_DR);
    236. 234 val |= (1 << 3);
    237. 235 writel(val, GPIO1_DR);
    238. 236
    239. 237 /* 注册字符设备驱动 */
    240. 238 /* 1、创建设备号 */
    241. 239 if (dtsled.major) { /* 定义了设备号 */
    242. 240 dtsled.devid = MKDEV(dtsled.major, 0);
    243. 241 register_chrdev_region(dtsled.devid, DTSLED_CNT,
    244. DTSLED_NAME);
    245. 242 } else { /* 没有定义设备号 */
    246. 243 alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT,
    247. DTSLED_NAME); /* 申请设备号 */
    248. 244 dtsled.major = MAJOR(dtsled.devid); /* 获取分配号的主设备号 */
    249. 245 dtsled.minor = MINOR(dtsled.devid); /* 获取分配号的次设备号 */
    250. 246 }
    251. 247 printk("dtsled major=%d,minor=%d\r\n",dtsled.major,
    252. dtsled.minor);
    253. 248
    254. 249 /* 2、初始化 cdev */
    255. 250 dtsled.cdev.owner = THIS_MODULE;
    256. 251 cdev_init(&dtsled.cdev, &dtsled_fops);
    257. 252
    258. 253 /* 3、添加一个 cdev */
    259. 254 cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
    260. 255
    261. 256 /* 4、创建类 */
    262. 257 dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
    263. 258 if (IS_ERR(dtsled.class)) {
    264. 259 return PTR_ERR(dtsled.class);
    265. 260 }
    266. 261
    267. 262 /* 5、创建设备 */
    268. 263 dtsled.device = device_create(dtsled.class, NULL, dtsled.devid,
    269. NULL, DTSLED_NAME);
    270. 264 if (IS_ERR(dtsled.device)) {
    271. 265 return PTR_ERR(dtsled.device);
    272. 266 }
    273. 267
    274. 268 return 0;
    275. 269 }
    276. 270
    277. 271 /*
    278. 272 * @description : 驱动出口函数
    279. 273 * @param : 无
    280. 274 * @return : 无
    281. 275 */
    282. 276 static void __exit led_exit(void)
    283. 277 {
    284. 278 /* 取消映射 */
    285. 279 iounmap(IMX6U_CCM_CCGR1);
    286. 280 iounmap(SW_MUX_GPIO1_IO03);
    287. 281 iounmap(SW_PAD_GPIO1_IO03);
    288. 282 iounmap(GPIO1_DR);
    289. 283 iounmap(GPIO1_GDIR);
    290. 284
    291. 285 /* 注销字符设备驱动 */
    292. 286 cdev_del(&dtsled.cdev);/* 删除 cdev */
    293. 287 unregister_chrdev_region(dtsled.devid, DTSLED_CNT);/*注销设备号*/
    294. 288
    295. 289 device_destroy(dtsled.class, dtsled.devid);
    296. 290 class_destroy(dtsled.class);
    297. 291 }
    298. 292
    299. 293 module_init(led_init);
    300. 294 module_exit(led_exit);
    301. 295 MODULE_LICENSE("GPL");
    302. 296 MODULE_AUTHOR("jia");

    3.编写测试APP

            内容和上一节内容完全一样。

    三、运行测试

    1.编译驱动程序和测试APP

            Makefile 内容如下:

    1. 1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek
    2. ......
    3. 4 obj-m := dtsled.o //特别注意
    4. ......
    5. 11 clean:
    6. 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

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

    make -j32

            输入如下指令编译测试ledApp.c这个程序,生成ledAPP这个程序。

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

    2.运行测试

            将编译出来的 dtsled.ko 和 ledApp 两个文件拷贝到 对应目录 中,输入如下命令加载dtsled.ko 驱动模块:

    1. depmod //第一次加载驱动的时候需要运行此命令
    2. modprobe dtsled.ko //加载驱动

            如果正常的话,在终端中会输出如下信息:

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

    ./ledApp /dev/dtsled 1

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

    //关闭 LED 灯

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

    rmmod dtsled.ko

    四.总结

            本笔记主要学习了基于设备树的LED驱动开发,重点是程序的编写,其内容又包含设备树文件的修改、驱动程序编写以及测试APP的编写。


    本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

  • 相关阅读:
    日志收集分析平台原理
    vr虚拟现实技术融入司法办案实操培训中的优势
    1281. 整数的各位积和之差
    「C++小游戏教程」猜数游戏
    【docker搭建pytorch环境及常用指令】
    ogg怎么转换成mp3格式?
    Java-JDK8-Stream操作笔记
    Opentelemetry SDK的简单用法
    C# 通用 HTTP 签名组件的另类实现
    【WSL】安装WSL和Docker-20220828
  • 原文地址:https://blog.csdn.net/jiage987450/article/details/132910993