设备树内容如下
除了需要/* 设备操作函数 */
还需要 /* 设备结构体 */
eg:
struct dtsled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
};
初始化注册函数也需要变化
初始化函数需要使用 of_find_node_by_path函数,找到设备树节点:
/* 1、获取设备节点:alphaled */
dtsled.nd = of_find_node_by_path("/alphaled");
初始化函数需要使用 of_find_property函数,找到设备树的兼容性属性
/* 2、获取compatible属性内容 */
proper = of_find_property(dtsled.nd, "compatible", NULL);
/* 3、获取status属性内容 */
ret = of_property_read_string(dtsled.nd, "status", &str);
获取寄存器的内容,很重要!!!
/* 4、获取reg属性内容 */
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
需要配置IO映射
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
GPIO1_DR = of_iomap(dtsled.nd, 3);
GPIO1_GDIR = of_iomap(dtsled.nd, 4);
of_iomap
void __iomem *of_iomap(struct device_node *node, int index);
通过设备树的设备结点直接进行设备内存区间的 ioremap(),index是内存段的索引。若设备结点的reg属性有多段,可通过index标示要ioremap的是哪一段,只有1段的情况, index为0。
采用Device Tree后,大量的设备驱动通过of_iomap()进行映射,而不再通过传统的ioremap
引用:https://blog.csdn.net/alimingh/article/details/111666429
在注册驱动使用MKDEV获取主设备号和次设备号
版本:linux-2.6.24.4
宏:
MKDEV(MAJOR, MINOR);
说明: 获取设备在设备表中的位置。
MAJOR 主设备号
MINOR 次设备号
内核使用的版本号说明文件:
在内核 /Documentation 目录下的 devices.txt 有说明。
一般本地保留的
MAJOR
234-239 UNASSIGNED
240-254 char LOCAL/EXPERIMENTAL USE
240-254 block LOCAL/EXPERIMENTAL USE
MINOR
1 ~ 250 (次设备号的 0 不能使用)
静态的设备文件建立:
mknod /dev/gpio_led c 240
利用register_chrdev_region函数注册
register_chrdev_region() 函数用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围,并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。
在2.4版本后,内核里就加入了以下几个函数也可以来实现注册字符设备:
分为了静态注册(指定设备编号来注册)、动态分配(不指定设备编号来注册),以及有连续注册的次设备编号范围区间,避免了register_chrdev()浪费资源的缺点
2.1:
/*指定设备编号来静态注册一个字符设备*/ int register_chrdev_region(dev_t from, unsigned count, const char *name);
from: 注册的指定起始设备编号,比如:MKDEV(100, 0),表示起始主设备号100, 起始次设备号为0
count:需要连续注册的次设备编号个数,比如: 起始次设备号为0,count=100,表示0~99的次设备号都要绑定在同一个file_operations操作方法结构体上
*name:字符设备名称
当返回值小于0,表示注册失败
2.2:
/*动态分配一个字符设备,注册成功并将分配到的主次设备号放入*dev里*/ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
*dev: 存放起始设备编号的指针,当注册成功, *dev就会等于分配到的起始设备编号,可以通过MAJOR()和MINNOR()函数来提取主次设备号
baseminor:次设备号基地址,也就是起始次设备号
count:需要连续注册的次设备编号个数,比如: 起始次设备号(baseminor)为0,baseminor=2,表示0~1的此设备号都要绑定在同一个file_operations操作方法结构体上
*name:字符设备名称
当返回值小于0,表示注册失败
eg:
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (dtsled.major) { /* 定义了设备号 */
dtsled.devid = MKDEV(dtsled.major, 0);
register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME); /* 申请设备号 */
dtsled.major = MAJOR(dtsled.devid); /* 获取分配号的主设备号 */
dtsled.minor = MINOR(dtsled.devid); /* 获取分配号的次设备号 */
}
/* 2、初始化cdev */
函数cdev_init()用于初始化一个静态分配的cdev结构体变量,函数cdev_init会自动初始化cdev->ops
对象,将函数的第二个输入参数赋值给cdev->ops
对象,不会初始化cdev->owner
对象,因此在经过函数cdev_alloc()和函数cdev_init()处理之后的cdev结构体变量,在应用程序中只需要给cdev->owner
对象赋值,此结构变量就可以被插入Linux内核系统了,作为一个可用的字符设备使用。
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
#include <linux/cdev.h>
初始化 cdev 后,需要把它添加到系统中去。为此可以调用 cdev_add()函数。传入cdev 结构的指针,起始设备编号,以及设备编号范围。
函数首先将分配的设备号与设备数目保存进cdev结构体中。然后再讲cdev结构体记录在一个 kobj_map 结构的 cdev_map 变量中。
/* 3、添加一个cdev */
cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
/* 4、创建类 */
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if (IS_ERR(dtsled.class)) {
return PTR_ERR(dtsled.class);
}
/* 5、创建设备 */
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if (IS_ERR(dtsled.device)) {
return PTR_ERR(dtsled.device);
}
注销设备驱动
/*
* @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(&dtsled.cdev);/* 删除cdev */
unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */
device_destroy(dtsled.class, dtsled.devid);
class_destroy(dtsled.class);
}