arm-linux 字符设备带设备树io操作

设备树内容如下

除了需要/* 设备操作函数 */

还需要 /* 设备结构体 */

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);

}

猜你喜欢

转载自blog.csdn.net/L1153413073/article/details/125486527