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

使用设备树来向 Linux 内核传递相关的寄存器物理地址, Linux 驱动文件使用上一章讲解的 OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的 IO。
在根节点“/”下创建一个名为“alphaled”的子节点,打开 imx6ull-alientek-emmc.dts 文件,在根节点“/”最后面输入如下所示内容:
- 1 alphaled {
- 2 #address-cells = <1>;
- 3 #size-cells = <1>;
- 4 compatible = "atkalpha-led";
- 5 status = "okay";
- 6 reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
- 7 0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
- 8 0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
- 9 0X0209C000 0X04 /* GPIO1_DR_BASE */
- 10 0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
- 11 };
特别关注一下第6-10行,reg 属性设置了驱动里面所要使用的寄存器物理地址。
输入如下命令,重新编译一下imx6ull-alientek-emmc.dts。
make dtbs
使用编译后的新的imx6ull-alientek-emmc.dtb启动Linux内核。启动成功后,在/proc/device-tree目录下会看到alphaled这个节点,如下:

直接上代码吧,如下:
- 1 #include
- 2 #include
- 3 #include
- 4 #include
- 5 #include
- 6 #include
- 7 #include
- 8 #include
- 9 #include
- 10 #include
- 11 #include
- 12 #include
- 13 #include
- 14 #include
- 15 #include
- 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 /* 映射后的寄存器虚拟地址指针 */
- 32 static void __iomem *IMX6U_CCM_CCGR1;
- 33 static void __iomem *SW_MUX_GPIO1_IO03;
- 34 static void __iomem *SW_PAD_GPIO1_IO03;
- 35 static void __iomem *GPIO1_DR;
- 36 static void __iomem *GPIO1_GDIR;
- 37
- 38 /* dtsled 设备结构体 */
- 39 struct dtsled_dev{
- 40 dev_t devid; /* 设备号 */
- 41 struct cdev cdev; /* cdev */
- 42 struct class *class; /* 类 */
- 43 struct device *device; /* 设备 */
- 44 int major; /* 主设备号 */
- 45 int minor; /* 次设备号 */
- 46 struct device_node *nd; /* 设备节点 */
- 47 };
- 48
- 49 struct dtsled_dev dtsled; /* led 设备 */
- 50
- 51 /*
- 52 * @description : LED 打开/关闭
- 53 * @param - sta : LEDON(0) 打开 LED, LEDOFF(1) 关闭 LED
- 54 * @return : 无
- 55 */
- 56 void led_switch(u8 sta)
- 57 {
- 58 u32 val = 0;
- 59 if(sta == LEDON) {
- 60 val = readl(GPIO1_DR);
- 61 val &= ~(1 << 3);
- 62 writel(val, GPIO1_DR);
- 63 }else if(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 */
- 77 static int led_open(struct inode *inode, struct file *filp)
- 78 {
- 79 filp->private_data = &dtsled; /* 设置私有数据 */
- 80 return 0;
- 81 }
- 82
- 83 /*
- 84 * @description : 从设备读取数据
- 85 * @param – filp : 要打开的设备文件(文件描述符)
- 86 * @param - buf : 返回给用户空间的数据缓冲区
- 87 * @param - cnt : 要读取的数据长度
- 88 * @param – offt : 相对于文件首地址的偏移
- 89 * @return : 读取的字节数,如果为负值,表示读取失败
- 90 */
- 91 static ssize_t led_read(struct file *filp, char __user *buf,
- size_t cnt, loff_t *offt)
- 92 {
- 93 return 0;
- 94 }
- 95
- 96 /*
- 97 * @description : 向设备写数据
- 98 * @param - filp : 设备文件,表示打开的文件描述符
- 99 * @param - buf : 要写给设备写入的数据
- 100 * @param - cnt : 要写入的数据长度
- 101 * @param – offt : 相对于文件首地址的偏移
- 102 * @return : 写入的字节数,如果为负值,表示写入失败
- 103 */
- 104 static ssize_t led_write(struct file *filp, const char __user *buf,
- size_t cnt, loff_t *offt)
- 105 {
- 106 int retvalue;
- 107 unsigned char databuf[1];
- 108 unsigned char ledstat;
- 109
- 110 retvalue = copy_from_user(databuf, buf, cnt);
- 111 if(retvalue < 0) {
- 112 printk("kernel write failed!\r\n");
- 113 return -EFAULT;
- 114 }
- 115
- 116 ledstat = databuf[0]; /* 获取状态值 */
- 117
- 118 if(ledstat == LEDON) {
- 119 led_switch(LEDON); /* 打开 LED 灯 */
- 120 } else if(ledstat == LEDOFF) {
- 121 led_switch(LEDOFF); /* 关闭 LED 灯 */
- 122 }
- 123 return 0;
- 124 }
- 125
- 126 /*
- 127 * @description : 关闭/释放设备
- 128 * @param – filp : 要关闭的设备文件(文件描述符)
- 129 * @return : 0 成功;其他 失败
- 130 */
- 131 static int led_release(struct inode *inode, struct file *filp)
- 132 {
- 133 return 0;
- 134 }
- 135
- 136 /* 设备操作函数 */
- 137 static struct 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 */
- 150 static int __init led_init(void)
- 151 {
- 152 u32 val = 0;
- 153 int ret;
- 154 u32 regdata[14];
- 155 const char *str;
- 156 struct property *proper;
- 157
- 158 /* 获取设备树中的属性数据 */
- 159 /* 1、获取设备节点: alphaled */
- 160 dtsled.nd = of_find_node_by_path("/alphaled");
- 161 if(dtsled.nd == NULL) {
- 162 printk("alphaled node can not found!\r\n");
- 163 return -EINVAL;
- 164 } else {
- 165 printk("alphaled node has been found!\r\n");
- 166 }
- 167
- 168 /* 2、获取 compatible 属性内容 */
- 169 proper = of_find_property(dtsled.nd, "compatible", NULL);
- 170 if(proper == NULL) {
- 171 printk("compatible property find failed\r\n");
- 172 } else {
- 173 printk("compatible = %s\r\n", (char*)proper->value);
- 174 }
- 175
- 176 /* 3、获取 status 属性内容 */
- 177 ret = of_property_read_string(dtsled.nd, "status", &str);
- 178 if(ret < 0){
- 179 printk("status read failed!\r\n");
- 180 } else {
- 181 printk("status = %s\r\n",str);
- 182 }
- 183
- 184 /* 4、获取 reg 属性内容 */
- 185 ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
- 186 if(ret < 0) {
- 187 printk("reg property read failed!\r\n");
- 188 } else {
- 189 u8 i = 0;
- 190 printk("reg data:\r\n");
- 191 for(i = 0; i < 10; i++)
- 192 printk("%#X ", regdata[i]);
- 193 printk("\r\n");
- 194 }
- 195
- 196 /* 初始化 LED */
- 197 #if 0
- 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、创建设备号 */
- 239 if (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\r\n",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);
- 258 if (IS_ERR(dtsled.class)) {
- 259 return PTR_ERR(dtsled.class);
- 260 }
- 261
- 262 /* 5、创建设备 */
- 263 dtsled.device = device_create(dtsled.class, NULL, dtsled.devid,
- NULL, DTSLED_NAME);
- 264 if (IS_ERR(dtsled.device)) {
- 265 return PTR_ERR(dtsled.device);
- 266 }
- 267
- 268 return 0;
- 269 }
- 270
- 271 /*
- 272 * @description : 驱动出口函数
- 273 * @param : 无
- 274 * @return : 无
- 275 */
- 276 static void __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("jia");
内容和上一节内容完全一样。
Makefile 内容如下:
- 1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek
- ......
- 4 obj-m := dtsled.o //特别注意
- ......
- 11 clean:
- 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
输入如下命令编译出驱动模块文件dtsled.ko:
make -j32
输入如下指令编译测试ledApp.c这个程序,生成ledAPP这个程序。
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
将编译出来的 dtsled.ko 和 ledApp 两个文件拷贝到 对应目录 中,输入如下命令加载dtsled.ko 驱动模块:
- depmod //第一次加载驱动的时候需要运行此命令
- modprobe dtsled.ko //加载驱动
如果正常的话,在终端中会输出如下信息:

驱动加载成功以后就可以使用 ledApp 软件来测试驱动是否工作正常,输入如下命令打开
LED 灯:
| //打开 LED 灯 |
| 输入上述命令以后观察 I.MX6U-ALPHA 开发板上的红色 LED 灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭 LED 灯: | |
| //关闭 LED 灯 |
输入上述命令以后观察 I.MX6U-ALPHA 开发板上的红色 LED 灯是否熄灭。如果要卸载驱
动的话输入如下命令即可:
rmmod dtsled.ko
本笔记主要学习了基于设备树的LED驱动开发,重点是程序的编写,其内容又包含设备树文件的修改、驱动程序编写以及测试APP的编写。
本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。