十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ARM Linux驱动开发

ARM Linux驱动开发 目录环境配置gcc安装配置NFS配置FileZilla安装交叉编译工具驱动开发分类字符设备驱动开发Linux设备号设备号操作函数字符设备开发步骤老版本字符设备开发新版本字符设备开发申请设备号注册字符设备自动创建设备节点设置mdev管理设备节点的创建与删除创建类并在该类下创建设备设置文件私有数据新版本字符驱动开发示例I.MX6ULL字符设备开发示例GPIO控制电子门锁注册设备树添加设备结点删除开发板原有关于GPIO1_IO03 或 gpio1 3的信息编译dtb文件编写字符设备驱动编译运行Linux设备树DTS语法向节点追加或修改内容特殊节点设备树常用 OF 操作函数设备树下的驱动开发设备树下LED驱动实验platform 设备驱动实验设备树下的platform驱动编写开发步骤总结pinctrl 和 gpio 子系统实验pinctrl 子系统驱动GPIO子系统使用 pinctrl 和 gpio 子系统示例总结Linux MISC 驱动实验开发步骤platform结合MISC驱动开发示例总结INPUT子系统开发步骤input_event结构体代码示例LCD驱动实验LCD驱动编写1. LCD屏幕IO配置2. LCD 屏幕参数节点信息修改3. LCD 屏幕背光节点信息Linux I2C 驱动实验I2C总线驱动(了解)I2C 设备驱动(掌握)i2c_client结构体i2c_driver结构体I2C 设备驱动编写流程I2C 设备驱动编写示例1. 修改设备树2.AP3216C 驱动编写Linux SPI 驱动实验SPI主机驱动spi_master 申请与释放spi_master 注册与注销SPI设备驱动spi_driver注册与注销SPI 设备和驱动的匹配SPI驱动开发示例1 修改设备树2编写 ICM20608 驱动Linux 多点电容触摸屏实验MT 协议Type A 触摸点信息上报时序Type B 触摸点信息上报时序多点触摸所使用到的 API 函数多点电容触摸驱动框架正点原子ATK7084屏幕驱动编写示例修改设备树编写多点电容触摸驱动环境配置gcc安装 正点原子教程中使用的Ubantu系统为16.04,我在安装时使用的是24.04(默认gcc版本为13),在内核编译时出现报错,gcc版本太高导致,因此需要安装低版本的gcc步骤: ①:更改配置文件(自Ubuntu24.04 (Noble Numbat) 起,APT 的软件源配置方式发生了变化,不再使用传统的 /etc/apt/sources.list 文件,而是采用新的 .sources 格式文件,默认路径为:/etc/apt/sources.list.d/ubuntu.sources)进入该目录修改配置信息。# 注释部分为原来的apt源信息 #Types: deb #URIs: http://archive.ubuntu.com/ubuntu/ #Suites: noble noble-updates noble-backports #Components: main restricted universe multiverse #Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg #Types: deb #URIs: http://security.ubuntu.com/ubuntu/ #Suites: noble-security #Components: main restricted universe multiverse #Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg # 添加的apt镜像 Types: deb URIs: http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ Suites: noble noble-updates noble-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg # gcc 5 的镜像安装源 Types: deb deb-src URIs: http://mirrors.aliyun.com/ubuntu Suites: xenial Components: main Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg ②:ls /usr/bin/gcc*查看系统中安装的gcc信息,gcc -v可以显示当前使用的gcc版本 ③:切换系统使用的gcc版本# 安装老版本gcc g++ sudo apt-get install gcc-5.4 g++-5.4 # 注册所有已安装的GCC版本到alternatives系统 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 50 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 # 交互式选择要使用的版本 sudo update-alternatives --config gcc # 配置 g++ sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 50 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 90 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 100 // 移除配置的gcc/g++ sudo update-alternatives --remove gcc /usr/bin/g++-11 sudo update-alternatives --remove gcc /usr/bin/g++-5 sudo update-alternatives --config g++ # Ubuntu22.04版本安装 # 添加 gcc-5 源 sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt update # 安装 gcc-5 sudo apt install gcc-5 g++-5根据信息选择2作为使用的gcc版本 ③:再次输入gcc -v验证是否切换到gcc-5配置NFSNFS是"网络文件系统"的缩写,允许计算机通过网络共享目录,就像访问本地硬盘一样Ubuntu 中的 NFS 服务,使用如下命令安装 NFS 服务:sudo apt-get install nfs-kernel-server rpcbind安装完成以后在用户根目录下创建一个名为“linux”的文件夹,以后所有的东西都放到这个“linux”文件夹里面,在linux下创建nfs文件夹配置 nfs,使用如下命令打开 nfs 配置文件/etc/exports:sudo vi /etc/exports #在打开文件中添加 /home/ft/linux/nfs *(rw,sync,no_root_squash)配置完成后重启NFS服务sudo /etc/init.d/nfs-kernel-server restart配置FileZilla使用ssh连接wsl# 安装ssh服务 sudo apt install openssh-server # 修改配置文件 sudo vim /etc/ssh/sshd_config # 取消以下注释 Port 22 ListenAddress 0.0.0.0 PermitRootLogin yes/prohibit-password PasswordAuthentication yes # 关闭wsl重启 启动+查看状态 sudo service ssh start service ssh status安装交叉编译工具安装正点原子提供的gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz建立 /usr/local/arm 拷贝压缩包到该目录下,解压sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz修改环境变量# 进入该文件 sudo vi /etc/profile # 文件结尾加入 export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin安装相关库sudo apt-get install lsb-core lib32stdc++6重启Ubuntu系统后验证arm-linux-gnueabihf-gcc -v驱动开发分类Linux 中的三大类驱动:字符设备驱动、块设备驱动和网络设备驱动字符设备驱动开发Linux设备号为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。 Linux 提供了一个名为 dev_t 的数据类型表示设备号。dev_t 定义在文件 include/linux/types.h 里面dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。这 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号, 低 20 位为次设备号。设备号操作函数在文件include/linux/kdev_t.h中提供了几个关于设备号的操作函数(本质是宏),如下所示:/* 宏 MINORBITS 表示次设备号位数,一共是 20 位。 宏 MINORMASK 表示次设备号掩码。 宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。 宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。 宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。 */ #define MINORBITS 20 #define MINORMASK ((1U MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) MINORMASK)) #define MKDEV(ma,mi) (((ma) MINORBITS) | (mi))字符设备开发步骤Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。模块的加载和卸载注册方式如下:// 函数原型 module_init(xxx_init); //注册模块加载函数 module_exit(xxx_exit); //注册模块卸载函数 /* 驱动入口函数 */ static int __init xxx_init(void) { /* 入口函数具体内容 */ return 0; } /* 驱动出口函数 */ static void __exit xxx_exit(void) { /* 出口函数具体内容 */ } /* 将上面两个函数指定为驱动的入口和出口函数 */ module_init(xxx_init); module_exit(xxx_exit);老版本字符设备开发字符设备驱动开发重点是使用 register_chrdev 函数注册字符设备,当不再使用设备的时候就使用 un register_chrdev 函数注销字符设备,驱动模块加载成功以后还需要手动使用 mknod 命令创建设备节点。新版本字符设备开发申请设备号如果没有指定设备号的话就使用如下函数来申请设备号:/* *dev:指向设备号的结构体 设备号 = 主设备号 + 次设备号 baseminor:起始次设备号 通常为 0 count: 需要连续分配多少个次设备号 name: 设备名称 */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *nam e如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:/* from: 设备号 count: 申请的设备数量 name:设备名称 */ int register_chrdev_region(dev_t from, unsigned count, const char *name)不管是通过 alloc_chrdev_region 函数还是 register_chr dev_region 函数申请的设备号,统一使用如下释放函数:void unregister_chrdev_region(dev_t from, unsigned count)注意:alloc_chrdev_region 和 register_chr dev_region 完成了申请/设置设备号 并完成注册;解除注册统一使用 unregister_chrdev_region。注册字符设备cdev定义在include/linux/cdev.h①:定义字符设备结构体变量并完成初始化// 字符设备结构体变量 重点关注 ops 和 dev 结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; // 字符设备文件操作函数集合 struct list_head list; dev_t dev; // 设备号 unsigned int count; }; /* 设备操作函数 */ 4 static struct file_operations test_fops = { 5 .owner = THIS_MODULE, 6 /* 其他具体的初始项 */ 7 }; // 初始化函数 void cdev_init(struct cdev *cdev, const struct file_operations *fops);②:向Linux系统添加字符设备 cdev_add/* 添加字符设备 count 添加的字符设备数量 */ int cdev_add(struct cdev *p, dev_t dev, unsigned count);③:卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备void cdev_del(struct cdev *p)自动创建设备节点设置mdev管理设备节点的创建与删除modprobe加载驱动程序以后还需要使用命令“mknod”手动创建设备节点。(加载成功后使用:cat /proc/devices可以看到当前系统中已经使用的设备号,需要在/dev下创建该设备的设备节点)。在 Linux 下通过 udev 来实现设备文件的创建与删除, udev 可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。使用busybox创建文件系统时,会创建一个udev的简化版本mdev。使用mdev实现设备节点的自动创建与删除:# 下面命令设置热插拔事件由 mdev 来管理 echo /sbin/mdev /proc/sys/kernel/hotplug创建类并在该类下创建设备函数定义在include/linux/device.h①:创建和删除类/* 创建类 struct class *class_create(struct module *owner, const char *name); ├─────────┬─┘ ├─────┬───┘ ├─────────────┬──────────┘ ├──────────┬──────┘ 返回值类型 函数名 第一个参数 第二个参数 owner: 一般为 THIS_MODULE, name: 是类名字 */ struct class *class_create (struct module *owner, const char *name); // 删除类 --在驱动的退出函数中销毁创建的类 void class_destroy(struct class *cls);②:在类下创建设备/* device_create 是个可变参数函数 class 就是设备要创建哪个类下面; parent 是父设备,一般为 NULL,也就是没有父设备; devt 是设备号;参数 drvdata 是设备可能会使用的一些数据,一般为 NULL; fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx 这个设备文件。返回值就是创建好的设备。 */ struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)卸载驱动时需要删除该设备。void device_destroy(struct class *class, dev_t devt)设置文件私有数据每个硬件设备都有一些属性,比如主设备号(dev_t),类(class)、设备(device)、开关状态(sta te)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式,如下所示:/* 设备结构体 */ struct test_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev 字符设备结构体 */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ }; struct test_dev testdev; /* open 函数 */ static int test_open(struct inode *inode, struct file *filp) { filp-private_data = testdev; /* 设置私有数据 */ return 0; }新版本字符驱动开发示例首先在C/C++: Edit configurations(JSON)z中包含Linux内核的头文件①:配置文件示例如下:{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**", "内核源码存放目录/include", "内核源码存放目录/arch/arm/include", "内核源码存放目录/arch/arm/include/generated" ], "defines": [], "compilerPath": "/usr/bin/gcc", "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "clang-x64" } ], "version": 4 }②:驱动程序编写示例:#include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include linux/cdev.h #include linux/device.h #include asm/mach/map.h #include asm/uaccess.h #include asm/io.h /*************************************************************** Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : newchrled.c 作者 : 左忠凯 版本 : V1.0 描述 : LED驱动文件。 其他 : 无 论坛 : www.openedv.com 日志 : 初版V1.0 2019/6/27 左忠凯创建 ***************************************************************/ #define NEWCHRLED_CNT 1 /* 设备号个数 */ #define NEWCHRLED_NAME "newchrled" /* 名字 */ #define LEDOFF 0 /* 关灯 */ #define LEDON 1 /* 开灯 */ /* 寄存器物理地址 */ #define CCM_CCGR1_BASE (0X020C406C) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004) /* 映射后的寄存器虚拟地址指针 */ static void __iomem *IMX6U_CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR; /* newchrled设备结构体 */ struct newchrled_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ }; struct newchrled_dev newchrled; /* led设备 */ /* * @description : LED打开/关闭 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED * @return : 无 */ void led_switch(u8 sta) { u32 val = 0; if(sta == LEDON) { val = readl(GPIO1_DR); val = ~(1 3); writel(val, GPIO1_DR); }else if(sta == LEDOFF) { val = readl(GPIO1_DR); val|= (1 3); writel(val, GPIO1_DR); } } /* * @description : 打开设备 * @param - inode : 传递给驱动的inode * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * @return : 0 成功;其他 失败 */ static int led_open(struct inode *inode, struct file *filp) { filp-private_data = newchrled; /* 设置私有数据 */ return 0; } /* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } /* * @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int retvalue; unsigned char databuf[1]; unsigned char ledstat; retvalue = copy_from_user(databuf, buf, cnt); if(retvalue 0) { printk("kernel write failed!\r\n"); return -EFAULT; } ledstat = databuf[0]; /* 获取状态值 */ if(ledstat == LEDON) { led_switch(LEDON); /* 打开LED灯 */ } else if(ledstat == LEDOFF) { led_switch(LEDOFF); /* 关闭LED灯 */ } return 0; } /* * @description : 关闭/释放设备 * @param - filp : 要关闭的设备文件(文件描述符) * @return : 0 成功;其他 失败 */ static int led_release(struct inode *inode, struct file *filp) { return 0; } /* 设备操作函数 */ static struct file_operations newchrled_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static int __init led_init(void) { u32 val = 0; /* 初始化LED */ /* 1、寄存器地址映射 */ IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4); SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4); SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4); GPIO1_DR = ioremap(GPIO1_DR_BASE, 4); GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4); /* 2、使能GPIO1时钟 */ val = readl(IMX6U_CCM_CCGR1); val = ~(3 26); /* 清楚以前的设置 */ val |= (3 26); /* 设置新值 */ writel(val, IMX6U_CCM_CCGR1); /* 3、设置GPIO1_IO03的复用功能,将其复用为 * GPIO1_IO03,最后设置IO属性。 */ writel(5, SW_MUX_GPIO1_IO03); /*寄存器SW_PAD_GPIO1_IO03设置IO属性 *bit 16:0 HYS关闭 *bit [15:14]: 00 默认下拉 *bit [13]: 0 kepper功能 *bit [12]: 1 pull/keeper使能 *bit [11]: 0 关闭开路输出 *bit [7:6]: 10 速度100Mhz *bit [5:3]: 110 R0/6驱动能力 *bit [0]: 0 低转换率 */ writel(0x10B0, SW_PAD_GPIO1_IO03); /* 4、设置GPIO1_IO03为输出功能 */ val = readl(GPIO1_GDIR); val = ~(1 3); /* 清除以前的设置 */ val |= (1 3); /* 设置为输出 */ writel(val, GPIO1_GDIR); /* 5、默认关闭LED */ val = readl(GPIO1_DR); val |= (1 3); writel(val, GPIO1_DR); /* 注册字符设备驱动 */ /* 1、创建设备号 */ if (newchrled.major) { /* 定义了设备号 */ newchrled.devid = MKDEV(newchrled.major, 0); register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME); } else { /* 没有定义设备号 */ alloc_chrdev_region(newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */ newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */ newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */ } printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor); /* 2、初始化cdev */ newchrled.cdev.owner = THIS_MODULE; cdev_init(newchrled.cdev, newchrled_fops); /* 3、添加一个cdev */ cdev_add(newchrled.cdev, newchrled.devid, NEWCHRLED_CNT); /* 4、创建类 */ newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME); if (IS_ERR(newchrled.class)) { return PTR_ERR(newchrled.class); } /* 5、创建设备 */ newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); if (IS_ERR(newchrled.device)) { return PTR_ERR(newchrled.device); } return 0; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static void __exit led_exit(void) { /* 取消映射 */ iounmap(IMX6U_CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); /* 注销字符设备驱动 */ cdev_del(newchrled.cdev);/* 删除cdev */ unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */ device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");宏作用IS_ERR(ptr)判断指针是否出错IS_ERR(ptr)把错误指针转为错误码(负数)适用场景class_create、device_create、kzalloc等返回指针的内核函数③:Makefile编写示例:KERNELDIR := 内核源码存放目录 CURRENT_PATH := $(shell pwd) obj-m := newchrled.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean // 执行过程 用户输入 make ↓ 读取当前目录Makefile ↓ 解析变量定义 ↓ 找到默认目标 "build" ↓ 发现依赖 "kernel_modules" ↓ 执行 kernel_modules 的命令 ↓ 调用内核构建系统 ↓ 进入内核源码目录 ↓ 读取内核主Makefile ↓ 返回模块源码目录 ↓ 编译 newchrled.c ↓ 生成 newchrled.ko ↓ 完成编译make -C 内核目录 M=当前驱动目录 modules进入内核目录 → 借用内核的编译环境 → 回来编译我这个驱动 → 生成 .ko 文件I.MX6ULL字符设备开发示例GPIO控制电子门锁pinctl+gpio子系统结合字符设备开发门锁控制设备,使用引脚为GPIO1_IO03,高电位打开门锁,低电位关闭门锁,使用开发板为正点原子I.MX6ULLALPHA正极接JP6中的GPIO1_IO03负极接GND,使用LED灯代替电子门锁注册设备树使用的设备树文件为imx6ull-14x14-emmc-7-1024x600-c.dts#include "imx6ull-14x14-evk-emmc.dts" i2c2 { goodix_ts@5d { reg = 0x5d; }; }; lcdif { display0: display { bits-per-pixel = 16; bus-width = 24; display-timings { native-mode = timing0; timing0: timing0 { clock-frequency = 51000000; hactive = 1024; vactive = 600; hfront-porch = 160; hback-porch = 140; hsync-len = 20; vback-porch = 20; vfront-porch = 12; vsync-len = 3; hsync-active = 0; vsync-active = 0; de-active = 1; pixelclk-active = 0; }; }; }; };找到imx6ull-14x14-evk-emmc.dts/* * Copyright (C) 2016 Freescale Semiconductor, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "imx6ull-14x14-evk.dts" usdhc2 { pinctrl-names = "default", "state_100mhz", "state_200mhz"; pinctrl-0 = pinctrl_usdhc2_8bit; pinctrl-1 = pinctrl_usdhc2_8bit_100mhz; pinctrl-2 = pinctrl_usdhc2_8bit_200mhz; bus-width = 8; non-removable; status = "okay"; };可以看到imx6ull-14x14-emmc-7-1024x600-c.dts包含了imx6ull-14x14-evk.dts,找到该文件,在该文件中进行修改添加设备结点// 在设备树根节点下添加 lock 设备节点 lock { compatible = "gpio-locks"; pinctrl-names = "default"; pinctrl-0 = pinctrl_gpio_locks; lock-gpios = gpio1 3 GPIO_ACTIVE_HIGH; status = "okay"; }; // iomuxc imx6ul-evk下 配置引脚复用模式以及电气属性 pinctrl_gpio_locks: gpio-locks { fsl,pins = MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0 ; };删除开发板原有关于GPIO1_IO03 或 gpio1 3的信息leds { compatible = "gpio-leds"; pinctrl-names = "default"; pinctrl-0 = pinctrl_beep; // 注释原有代码 添加这个 /*pinctrl-0 = pinctrl_gpio_leds pinctrl_beep;*/ /*led1{ // 注释关于GPIO1_IO03的信息 label = "sys-led"; gpios = gpio1 3 GPIO_ACTIVE_LOW; linux,default-trigger = "heartbeat"; default-state = "off"; };*/ beep{ label = "beep"; gpios = gpio5 1 GPIO_ACTIVE_LOW; default-state = "off"; }; }; // iomuxc中的gpio1 3的配置信息注释掉 /*pinctrl_gpio_leds: gpio-leds { fsl,pins = MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x17059 ; };*/编译dtb文件重新编译设备树文件,编译完成后将./tmp中所有dtb文件拷贝到开发板/home/root目录中将开发板使用的dtb文件(例如我使用的是imx6ull-14x14-emmc-7-1024x600-c.dtb)拷贝到/run/media/mmcblk1p1/下重新启动开发板,查看/proc/device-tree下是否生成设备节点编写字符设备驱动#include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include linux/cdev.h #include linux/device.h #include linux/of.h #include linux/of_address.h #include linux/of_gpio.h #include asm/mach/map.h #include asm/uaccess.h #include asm/io.h #define GPIOLOCK_CNT 1 #define GPIOLOCK_NAME "gpiolock" #define LOCKON 1 #define LOCKOFF 0 // 定义gpiolock设备结构体 struct gpiolock_dev { dev_t devid; struct cdev cdev; struct class *class; struct device *device; int major; int minor; struct device_node *nd; int lock_gpio; }; struct gpiolock_dev gpiolock; static int lock_open(struct inode *inode, struct file *filp) { filp-private_data = gpiolock; return 0; } static ssize_t lock_write(struct file *flip, const char __user *buf, size_t cnt, loff_t *offt) { unsigned char databuf; if (copy_from_user(databuf, buf, 1)) return -EFAULT; if (databuf == 1) { gpio_set_value(gpiolock.lock_gpio, 1); // open the door } else { gpio_set_value(gpiolock.lock_gpio, 0); } return 0; } static int lock_close(struct inode *inode, struct file *flip) { return 0; } /* 设备操作函数结构体*/ static struct file_operations gpiolock_fops = { .owner = THIS_MODULE, .open = lock_open, .write = lock_write, .release = lock_close, }; static int __init lock_init(void) { int ret = 0; /* 1. 配置gpio方向 默认电平 */ gpiolock.nd = of_find_node_by_path("/lock"); if (gpiolock.nd == NULL) { printk("gpiolock node not find\n"); return -EINVAL; } else { printk("gpiolock node find\n"); } gpiolock.lock_gpio = of_get_named_gpio(gpiolock.nd, "lock-gpios", 0); if (gpiolock.lock_gpio 0) { printk("can't get lock-gpio\n"); return -EINVAL; } printk("lock-gpio num = %d\r\n", gpiolock.lock_gpio); // 设备树中设置高电平有效 设置为1 默认开门 ret = gpio_request(gpiolock.lock_gpio, "lock-gpio"); if (ret 0) { printk("gpio request failed\n"); return ret; } ret = gpio_direction_output(gpiolock.lock_gpio, 1); if (ret 0) { printk("can't set gpio\n"); return -EINVAL; } /* 2. 申请设备号注册字符设备 */ if (gpiolock.major) { gpiolock.devid = MKDEV(gpiolock.major, 0); register_chrdev_region(gpiolock.devid, GPIOLOCK_CNT, GPIOLOCK_NAME); } else { alloc_chrdev_region(gpiolock.devid, 0, GPIOLOCK_CNT, GPIOLOCK_NAME); gpiolock.major = MAJOR(gpiolock.devid); gpiolock.minor = MINOR(gpiolock.minor); } printk("gpiolock major:%d, minor:%d\r\n", gpiolock.major, gpiolock.minor); /* 3. 初始化字符设备并添加 */ gpiolock.cdev.owner = THIS_MODULE; cdev_init(gpiolock.cdev, gpiolock_fops); cdev_add(gpiolock.cdev, gpiolock.devid, GPIOLOCK_CNT); /* 4. 创建类 */ gpiolock.class = class_create(THIS_MODULE, GPIOLOCK_NAME); if(IS_ERR(gpiolock.class)) { return PTR_ERR(gpiolock.class); } /* 5. 创建设备 */ gpiolock.device = device_create(gpiolock.class, NULL, gpiolock.devid, NULL, GPIOLOCK_NAME); if(IS_ERR(gpiolock.device)) { return PTR_ERR(gpiolock.device); } return 0; } static void __exit lock_exit(void) { cdev_del(gpiolock.cdev); unregister_chrdev_region(gpiolock.devid, GPIOLOCK_CNT); device_destroy(gpiolock.class, gpiolock.devid); class_destroy(gpiolock.class); } module_init(lock_init); module_exit(lock_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xiaoyi");Makefile文件KERNRLDIR := /home/xiaoyi/IMX6/linux-imx-4.1.15-2.1.0 CURRENT_PATH := $(shell pwd) obj-m := lock_driver.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNRLDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNRLDIR) M=$(CURRENT_PATH) clean应用程序编写#include stdio.h #include stdlib.h #include fcntl.h #include unistd.h #include string.h #define DEV_PATH "/dev/gpiolock" int main(int argc, char *argv[]) { int fd; unsigned char cmd; // 判断参数 if (argc != 2) { printf("用法:\n"); printf(" %s 1 打开锁/亮灯\n", argv[0]); printf(" %s 0 关闭锁/灭灯\n", argv[0]); return -1; } // 打开设备 fd = open(DEV_PATH, O_WRONLY); if (fd 0) { perror("打开设备失败"); return -1; } // 获取命令 1 或 0 cmd = atoi(argv[1]); // 写入驱动 write(fd, cmd, 1); printf("执行成功: %s\n", cmd ? "已打开" : "已关闭"); close(fd); return 0; }编译运行驱动编译:直接执行make即可应用程序编译:arm-linux-gnueabihf-gcc lockAPP.c -o lockAPP运行结果Linux设备树设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(D evice Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如 CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等。DTS 是设备树源码文件, DTB 是将DTS 编译以后得到的二进制文件。需要用到 DTC 工具! DTC 工具源码在 Linux 内核的 scripts/dtc 目录下。在内核源码根目录下,如果只编译DTS文件使用make dtbs 命令DTS语法 ①:可以通过“#include”来引用.h、 .dtsi 和.dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi 后缀。(一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、 IIC 等等。)②:设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。 ③:“/”是根节点,每个设备树文件只有一个根节点。imx6u ll.dtsi 和 imx6ull-alientek-emmc.dts 这两个文件都有一个“/”根节点,这两个“/”根节点的内容会合并成一个根节点。 ④:label: node-name@unit-address中label表示标签,其中“node-name”是节点名字,unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。 ⑤:compatible 属性用于将设备和驱动绑定起来。compatible 属性的值格式如下所示:// 其中 manufacturer 表示厂商, model 一般是模块对应的驱动名字。 "manufacturer,model"向节点追加或修改内容// 在开发板的dts下进行修改 i2c1 { /* 要追加或修改的内容 */ };Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/devicetree 目录下根据节点名字创建不同文件夹。特殊节点aliases 子节点:单词 aliases 的意思是“别名”,
返回列表