目录
linux设备驱动开发总体框架
以使用pinctrl
和gpio子系统
来开发的GPIO驱动
来描述本节.再具体就是点灯,去他妈点了一个月的灯
相对于32裸机,完成GPIO驱动需要先设置某个PIN的复用功能,速度和上下拉,再设置PIN所对应的GPIO。而linux驱动讲究驱动分离和分层,所以就针对PIN的配置推出了pinctrl子系统
,对于GPIO的配置推出了gpio子系统
。
pinctrl子系统
的源码路径为 drivers/pinctrl
pinctrl子系统
的功能:
- 获取设备树中pin信息
- 根据获取到的pin信息来设置pin的复用功能
- 根据获取到的pin信息来设置pin的电气属性(上下拉,速度,驱动能力等)
gpio子系统
目的是方便驱动开发者使用gpio:
- 在设备树中添加gpio相关信息
- 在驱动程序中使用gpio子系统提供的API函数来操作GPIO
1.修改设备树
要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息
- 添加pinctrl节点
I.MX6ULL的IOMUXC外设(核心功能是IO口复用)对应的节点就是iomuxc,在imx6ull.dtsi和imx6ull-alientek-emmc.dts中有。不同的外设如SPI接口和IIC接口等,使用的PIN不同,配置也不同,因此需要将某个外设使用的所有PIN都集合到一个子节点中,以group为单位,访问、控制多个pin,这就是pin groups
,所以一般命名会加上grp
本节要在 iomuxc 中添加我们自定义外设的 PIN,那么需要在iomuxc下新建一个子节点,然后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。这里只有一个
pinctrl_led: ledgrp{
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
>;
};
将 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03,电气属性值为 0X10B0
申请IO的时候失败,大部分原因是这个IO被其他外设占用了,小部分原因是检查pinctrl设置。检查设备树,查找有哪些使用同一IO的设备,然后注释
- 添加设备节点 LED
在根节点下创建LED节点,然后设置对应的pinctrl节点为上面添加的子节点。设置属性状态名字等
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
}
注:改到这里可以make dtbs之后将.dtb放入tftp中,网络启动,去/proc/device-tree中查看是否有节点
2.驱动出口驱动入口
2.1 入口:
注册字符设备驱动
1、创建设备号
register_chrdev_region//给定设备号
alloc_chrdev_region//动态分配设备号
2、初始化并添加一个cdev
cdev_init
cdev_add
3、创建类和创建设备
这个目的是自动创建设备节点
class_create
device_create
内核中定义了struct class
结构体,内核同时提供了class_create()
函数,可以用它来创建一个类(本质类型是结构体),创建好这个类,再调用device_create()
函数来在/dev
目录下创建相应的设备节点。
这样,加载模块的时候,用户空间(因为原子使用的Busybox
创建的根文件系统使用了udev
的简化版本mdev
)中的mdev
会自动响应device_create()
函数,去寻找对应的类从而创建设备节点
设置LED所使用的GPIO
1、获取设备节点:gpioled
of_find_node_by_path
2、 获取设备树中的gpio属性,得到LED所使用的LED编号
of_get_named_gpio
3、 申请IO
不申请也能用,但是无法被检测IO是否被使用,所以最好还是申请一下
4、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯
gpio_direction_output
2.2 出口:
注销字符设备驱动,释放申请的设备号等
unregister_chrdev_region//设备号释放函数
device_destroy //删除设备
class_destroy //删除类
3.定义设备结构体
保存设备基本信息的结构体,一般包括
/* gpioled设备结构体 */
struct gpioled_dev{
dev_t devid; /* 设备号 dev_t是一个数据类型,unsigned int */
struct cdev cdev; /* cdev 描述字符设备 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* led所使用的GPIO编号 */
};
然后声明一个变量作为驱动
struct gpioled_dev gpioled; /* led设备 */
4.实现设备操作函数,
linux内核为设备建立一个设备文件,这样就使得对设备文件的所有操作,就相当于对设备的操作.其实也就是应用程序
file_operations 结构体就是设备的具体操作函数,原型路径·include\linux\fs.h
步骤:
- 定义file_operations结构体类型的变量gpioled_fops
- 分析设备需求,就是需要对设备进行那些操作(打开,关闭,读写)
- 确定操作后,实现操作函数
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/* 打开设备 */
static int chrtest_open(struct inode *inode, struct file *filp)
{ /* 用户实现具体功能 */
return 0;
}
/* 从设备读取 */
static ssize_t chrtest_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
return 0;
}
/* 向设备写数据 */
static ssize_t chrtest_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
return 0;
}
/* 释放设备 */
static int chrtest_release(struct inode *inode, struct file *filp)
{
return 0;
}
5.编写设备测试APP
编写测试 APP 就是编写 Linux 应用,运行在用户空间,需要用到 C 库里面和文件操作有关的一些函数,比如open、read、write 和 close 这四个函数。
5.1编写
5.2编译
因为是运行在ARM开发板上,所以使用arm版本的编译器
arm-linux-gnueabihf-gcc ledApp.c -o LEDAPP
5.3运行
./LEDAPP /dev/gpioled 1
6.其他
最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否
则的话编译的时候会报错,作者信息可以添加也可以不添加
MODULE_LICENSE("GPL") //添加LICENSE信息,GPL等
MODULE_AUTHOR("chengx") //添加作者