基于zynqMP15eg的linux驱动学习

第1章.linux LED驱动:

init中:gpio相关寄存器内存映射ioremap-gpio配置-注册字符设备register-chrdev

exit中:注销字符设备unregister_chrdev------取消内存映射iounmap

write中:copy_from_user获取用户程序输入值------readl读取data寄存器值为val------根据用户程序传入的值将val的12位写0或者写1------writel写入data寄存器

复制代码
#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 <asm/mach/map.h>*/
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_MAJOR               200         /* 主设备号 */
#define LED_NAME                "led"       /* 设备名字 */

/* 
 * GPIO相关寄存器地址定义
 */
#define ZYNQ_GPIO_REG_BASE      0xFF0A0000
#define DATA_OFFSET             0x00000044
#define DIRM_OFFSET             0x00000244
#define OUTEN_OFFSET            0x00000248
#define INTDIS_OFFSET           0x00000254
#define APER_CLK_CTRL           0xFF5E00AC

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *data_addr;
static void __iomem *dirm_addr;
static void __iomem *outen_addr;
static void __iomem *intdis_addr;
static void __iomem *aper_clk_ctrl_addr;


/*
 * @description         : 打开设备
 * @param -- inode       : 传递给驱动的inode
 * @param - filp        : 设备文件,file结构体有个叫做private_data的成员变量
 *                        一般在open的时候将private_data指向设备结构体。
 * @return              : 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
    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 ret;
    int val;
    char kern_buf[1];

    ret = copy_from_user(kern_buf, buf, cnt);// 得到应用层传递过来的数据
    if(0 > ret) {
        printk(KERN_ERR "kernel write failed!\r\n");
        return -EFAULT;
    }

    val = readl(data_addr);
    if(0 == kern_buf[0])
        val &= ~(0x1U << 12);    // 如果传递过来的数据是0则关闭led
    else if (1 == kern_buf[0])
        val |= (0x1U << 12);     // 如果传递过来的数据是1则点亮led

    writel(val, data_addr);
    return 0;        
}

/*
 * @description         : 关闭/释放设备
 * @param -- filp        : 要关闭的设备文件(文件描述符)
 * @return              : 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
    .owner  =   THIS_MODULE,
    .open   =   led_open,
    .read   =   led_read,
    .write  =   led_write,
    .release=   led_release,
};

static int __init led_init(void)
{
    u32 val;
    int ret;

    /* 1.寄存器地址映射 */
    data_addr = ioremap(ZYNQ_GPIO_REG_BASE + DATA_OFFSET, 4);
    dirm_addr = ioremap(ZYNQ_GPIO_REG_BASE + DIRM_OFFSET, 4);
    outen_addr = ioremap(ZYNQ_GPIO_REG_BASE + OUTEN_OFFSET, 4);
    intdis_addr = ioremap(ZYNQ_GPIO_REG_BASE + INTDIS_OFFSET, 4);
    aper_clk_ctrl_addr = ioremap(APER_CLK_CTRL, 4);

    /* 2.使能GPIO时钟 */
    val = readl(aper_clk_ctrl_addr);
    val |= (0x1U << 24);
    writel(val, aper_clk_ctrl_addr);

    /* 3.关闭中断功能 */
    val |= (0x1U << 12);
    writel(val, intdis_addr);

    /* 4.设置GPIO为输出功能 */
    val = readl(dirm_addr);
    val |= (0x1U << 12);
    writel(val, dirm_addr);

    /* 5.使能GPIO输出功能 */
    val = readl(outen_addr);
    val |= (0x1U << 12);
    writel(val, outen_addr);

    /* 6.默认关闭LED */
    val = readl(data_addr);
    val &= ~(0x1U << 12);
    writel(val, data_addr);

    /* 7.注册字符设备驱动 */
    ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if(0 > ret){
        printk(KERN_ERR "Rsgister LED driver failed!\r\n");
        return ret;
    }

    return 0;
}

static void __exit led_exit(void)
{
    /* 1.卸载设备 */
    unregister_chrdev(LED_MAJOR, LED_NAME);

    /* 2.取消内存映射 */
    iounmap(data_addr);
    iounmap(dirm_addr);
    iounmap(outen_addr);
    iounmap(intdis_addr);
    iounmap(aper_clk_ctrl_addr);
}

/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("DengTao <773904075@qq.com>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

第2章.新字符设备驱动实验

改进点:(1)使用 Linux 内核推荐的新字符设备驱动API 函数注册和注销字符设备(2)在驱动模块加载的时候自动创建设备节点文件

1.创建设备结构体 newchrled_dev

struct newchrled_dev{

dev_t devid; /* 设备号 */

struct cdev cdev; /* cdev */

struct class *class; /* 类 */

struct device *device; /* 设备 */

int major; /* 主设备号 */

int minor; /* 次设备号 */

};

static struct newchrled_dev newchrled; /* led设备 */

2.led_open 函数中设置文件的私有数据 private_data 指向 newchrdev。

init中:

(1)地址映射ioremap

(2)gpio配置

(3)注册设备号

if(newchrled.major) {

newchrled.devid = MKDEV(newchrled.major, 0);

ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);

if (ret)

goto out1;

} else {

ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);

if (ret)

goto out1;

newchrled.major = MAJOR(newchrled.devid);

newchrled.minor = MINOR(newchrled.devid);

}

(4)注册cdev

newchrled.cdev.owner = THIS_MODULE;

cdev_init(&newchrled.cdev, &newchrled_fops);

/* 添加一个cdev */

ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

if (ret)

goto out2;

(5)创建类

newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);

if (IS_ERR(newchrled.class)) {

ret = PTR_ERR(newchrled.class);

goto out3;

}

(6)创建设备

newchrled.device = device_create(newchrled.class, NULL,

newchrled.devid, NULL, NEWCHRLED_NAME);

if (IS_ERR(newchrled.device)) {

ret = PTR_ERR(newchrled.device);

goto out4;

}

复制代码
#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 <asm/mach/map.h>*/
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>

#define NEWCHRLED_CNT           1               /* 设备号个数 */
#define NEWCHRLED_NAME          "newchrled"     /* 名字 */

/* 
 * GPIO相关寄存器地址定义
 */
#define ZYNQ_GPIO_REG_BASE          0xFF0A0000
#define DATA_OFFSET                 0x00000044
#define DIRM_OFFSET                 0x00000244
#define OUTEN_OFFSET                0x00000248
#define INTDIS_OFFSET               0x00000254
#define APER_CLK_CTRL               0xFF5E00AC

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *data_addr;
static void __iomem *dirm_addr;
static void __iomem *outen_addr;
static void __iomem *intdis_addr;
static void __iomem *aper_clk_ctrl_addr;

/* newchrled设备结构体 */
struct newchrled_dev{
    dev_t devid;                /* 设备号 */
    struct cdev cdev;           /* cdev */
    struct class *class;        /* 类 */
    struct device *device;      /* 设备 */
    int major;                  /* 主设备号 */
    int minor;                  /* 次设备号 */
};

static struct newchrled_dev newchrled;  /* led设备 */

/*
 * @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 ret;
    int val;
    char kern_buf[1];

    ret = copy_from_user(kern_buf, buf, cnt);   // 得到应用层传递过来的数据
    if (0 > ret){
        printk(KERN_ERR "kernel write failed!\r\n");
        return -EFAULT;
    }

    val = readl(data_addr);
    if(0 == kern_buf[0])
        val &= ~(0x1U << 12);    // 如果传递过来的数据是0则关闭led
    else if (1 == kern_buf[0])
        val |= (0x1U << 12);     // 如果传递过来的数据是1则点亮led    
    
    writel(val, data_addr);
    return 0;
}

/*
 * @description         : 关闭/释放设备
 * @param -- filp        : 要关闭的设备文件(文件描述符)
 * @return               : 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static inline void led_ioremap(void)
{
    data_addr = ioremap(ZYNQ_GPIO_REG_BASE + DATA_OFFSET, 4);
    dirm_addr = ioremap(ZYNQ_GPIO_REG_BASE + DIRM_OFFSET, 4);
    outen_addr = ioremap(ZYNQ_GPIO_REG_BASE + OUTEN_OFFSET, 4);
    intdis_addr = ioremap(ZYNQ_GPIO_REG_BASE + INTDIS_OFFSET, 4);
    aper_clk_ctrl_addr = ioremap(APER_CLK_CTRL, 4);
}   

static inline void led_iounmap(void)
{
    iounmap(data_addr);
    iounmap(dirm_addr);
    iounmap(outen_addr);
    iounmap(intdis_addr);
    iounmap(aper_clk_ctrl_addr);
}

/* 设备操作函数 */
static struct file_operations newchrled_fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .read    = led_read,
    .write   = led_write,
    .release = led_release,
};

static int __init led_init(void)
{
    u32 val;
    int ret;

    /* 1.寄存器地址映射 */
    led_ioremap();

    /* 2.使能GPIO时钟 */
    val = readl(aper_clk_ctrl_addr);
    val |= (0x1U << 24);
    writel(val, aper_clk_ctrl_addr);

    /* 3.关闭中断功能 */
    val |= (0x1U << 12);
    writel(val, intdis_addr);

    /* 4.设置GPIO为输出功能 */
    val = readl(dirm_addr);
    val |= (0x1U << 12);
    writel(val, dirm_addr);

    /* 5.使能GPIO输出功能 */
    val = readl(outen_addr);
    val |= (0x1U << 12);
    writel(val, outen_addr);

    /* 6.默认关闭LED */
    val =readl(data_addr);
    val &= ~(0x1U << 12);
    writel(val, data_addr);

    /* 7.注册字符设备驱动 */
     /* 创建设备号 */
     if(newchrled.major) {
        newchrled.devid = MKDEV(newchrled.major, 0);
        ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
        if (ret)
            goto out1;
     } else {
        ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
        if (ret)
            goto out1;

        newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MINOR(newchrled.devid);
     }

     printk("newcheled major=%d,minor=%d\r\n", newchrled.major, newchrled.minor);

     /* 初始化cdev */
     newchrled.cdev.owner = THIS_MODULE;
     cdev_init(&newchrled.cdev, &newchrled_fops);

    /* 添加一个cdev */
    ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
    if (ret)
        goto out2;
    
    /* 创建类 */
    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.class)) {
        ret = PTR_ERR(newchrled.class);
        goto out3;
    }

    /* 创建设备 */
    newchrled.device = device_create(newchrled.class, NULL,
                newchrled.devid, NULL, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.device)) {
        ret = PTR_ERR(newchrled.device);
        goto out4;
    }

    return 0;

out4:
    class_destroy(newchrled.class);

out3:
    cdev_del(&newchrled.cdev);

out2:
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);

out1:
    led_ioremap();

    return ret;
}

static void __exit led_exit(void)
{
    /* 注销设备 */
    device_destroy(newchrled.class, newchrled.devid);

    /* 注销类 */
    class_destroy(newchrled.class);

    /* 删除cdev */
    cdev_del(&newchrled.cdev);

    /* 注销设备号 */
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);

    /* 取消地址映射 */
    led_ioremap();
}

/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("DengTao <773904075@qq.com>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

第3章.linux设备树

3.1.优势

用于实现驱动代码与设备信息相分离,引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。这样编译只需要编译设备树,加快效率。

3.2.本篇驱动开发所使用的板级设备树文件

就是 arch/arm64/boot/dts/xilinx 目录下,包括 system-top.dts、pl.dtsi、pcw.dtsi、zyqnmp.dtsi、zynqmp-clk-conf.dtsi 和用户自定义设备树 system-user.dtsi 等。注意,在设备树中添加设备时,我们推荐放在 system-user.dtsi 中。(dts是设备树文件;dtsi文件其实就类似于 C 语言当中的.h 头文件,可以通过 C 语言中使用 include 来包含一个.dtsi 文件;dtb是dts编译后的二进制文件,我们需要这个,dtc是设备树文件.dts 的编译器)(zynqmp.dtsi 文件中的内容是 ZYNQ MPSoC 系列处理器相同的硬件外设配置信息(PS 端的),pl.dtsi 的内容是我们在 vivado 当中添加的 pl 端外设对应的配置信息,而 pcw.dtsi 则表示我们在 vivado 当中已经使能的 PS 外设,zynqmp-clk-ccf.dtsi 文件是 ZYNQ MPSoC 系列芯片通用的时钟相关的设备树文件)

3.3.使 用 make dtbs 命 令

在 内 核 源 码arch/arm64/boot/dts/xilinx/目录下有很多的 dts 文件,那我们编译的时候如何确定编译的是哪个或者说哪些 dts 文件的呢?大家可以打开 arch/arm64/boot/dts/xilinx/Makefile 文件,当运行 " make dtbs " 命令时,该makefile包含的设备树都会被编(我们要编译设备树"system-top.dts"文件,只要把"system-top.dtb"添加到Makefile 中就行。)

3.4.设备树结构特征

(1)一个 device tree 文件中只有一个 root node(根节点)(2)除了 root node,每个 node 都只有一个 parent node(父节点)(3)一般来说,开发板上的每一个设备都能够对应到设备树中的一个 node(4)每个 node 中包含了若干的 property-value(键-值对,当然也可以没有 value) 来描述该 node 的一些特性(5)每个 node 都有自己的 node name(节点名字)(6)node 之间可以是平行关系,也可以嵌套成父子关系,这样就可以很方便的描 述设备间的关系


解释:例如 node2 表示 ZYNQ PS 的一个 I2C 控制器,而 node3 则表示挂在该 I2C 总线下的某个设备,例如 eeprom、RTC 等。

3.5.设备树节点属性

(1)compatible 属性("<制造商>,<型号>")

compatible = "xlnx,xuartps", "cdns,uart-r1p8";

compatible 属性用于将设备和驱动绑定起来,例如该设备首先使用第一个兼容值 (xlnx,xuartps)在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值(cdns,uart-r1p8)查找,直到找到或者查找完整个 Linux 内核也没有找到对应的驱动。(一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备树中的节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动)

(2)model 属性值

model ="Alientek Zynq MPSoC Development Board";

model 属性值是一个字符串描述信息,内核在解析设备树的时候会把这个属性对应的字符串信息打印出来。

(3)status 属性

status 标识了设备的状态,使用 status 可以去禁止设备或者启用设备,看下设备树规范中的 status 可选值。

(4),#address-cells 和#size-cells

(5)reg属性

reg 属性的值一般是(address,length),其中 address 是起始地址,length 是地址长度。

(5)根节点 compatible 属性

针对 Xilinx Zynq MPSoC 系列的板卡,根节点"/"的 compatible 属性内容则为"xlnx,zynqmp"。当我们修改了根节点 compatible 属性内容以后,因为 Linux 内核找不到对应的硬件平台,因此 Linux 内核无法启动。

Linux 内核通过根节点 compatible 属性找到对应的 machine_desc 结构体的函数调用过程。

(6)引用节点

节点的命名格式如下所示,引入 label 的目的就是为了方便访问节点,可以直 接通过&label 来访问这个节点。

label:\]node-name\[@unit-address

比如节 点"intc: interrupt-controller@00a01000",节点 label 是 intc,而节点名字就很长了,为 "interrupt-controller@00a01000"。很明显通过&intc 来访问"interrupt-controller@00a01000" 这个节点要方便很多。

3.6.添加新设备到设备树:

任务:假设在 Zynq7000 系列的开发板上有一个 eeprom 器件(24c64)和一个 rtc 器件(pcf8563),它俩都 是挂在 ZYNQ 的 i2c0 总线下的。那么现在要把这两个设备添加到 i2c0 总线下。

方法:我们需要在 system-user.dtsi 文件中通过 &label 来访问节点完成数据追加的内容如下。


使用"&i2c0"引用i2c0,第 34~48 行就是向 i2c0 节点添加/修改数据,比如 35 的属性"clock-frequency = <100000>" 就表示将 i2c0 的时钟设置为 100KHz,"clock-frequency"就是新添加的属性。 第 36 行,将 status 属性的值由原来的 disabled 改为 okay,这是修改节点的属性值。 第 38~47 行,我们向 i2c0 子节点追加了两个子节点,"24c64@50"和"rtc@51"。

3.7.特殊节点

在根节点"/"中有那么几个特殊的子节点:aliases、chosen 以及 memory。
(1)aliases节点

aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。例如前面说到的使用"&label"的方式访问设备树中的节点,而是内核当中方便定位节点,例如在内核中通过 ethernet0 就可以定位到 gem0 节点(&gem0 引用的节点),再例如内核通过 serial0 就可以找到 uart0 节点。
(2)chosen 节点
chosen 节点一般会有两个属性,"bootargs"和"stdout-path"。

在 chosen 节点当中,属性 stdout-path = "serial0:115200n8",表示标准输出设备使用串
口 serial0,在 system-top.dts 文件当中,serial0 其实是一个别名,指向的就是 uart0;"115200"
则表示串口的波特率为 115200,"n"表示无校验位,"8"则表示有 8 位数据位。
console=ttyPS0,115200,ttyPS0 其实指的就是根文件系统下的/dev/ttyPS0 这个设备文
件,那么它对应的硬件设备其实就是板子的 uart0,root就是指定根文件系统路径。

对于我们使用这个 U-Boot 来说,它并没有定义 bootargs 环境变量,所以使用的就是内核设备树 chosen 节点下的 bootargs 属性,也就是说 U-Boot 的环境变量 bootargs 拥有最高的优先级。U-Boot 的 bootcmd 命令最终会执行 bootz 命令,而 bootz命令启动 Linux 内核的时候会运行 do_bootm_linux 函数,do_bootm_linux 函数会通过一系列复杂的调用,最终通过 fdt_chosen 函数在内核设备树chosen 节点中添加 bootargs 属性。
(3)memory 节点

memory 节点描述了系统内存的基地址以及系统内存大小,"reg = <0x0 0x20000000>"
就表示系统内存的起始地址为 0x0,大小为 0x20000000,也就是 512MB,该节点一般只有这
两个属性,device_type 属性的值固定为"memory"。

3.8 设备树在系统中的体现

Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的
/proc/device-tree 目录下根据节点名字创建不同文件夹。

3.9 设备树详细文档

:Linux 源码目录/Documentation/devicetree/bindings

3.10设备树常见of操作函数

设备都是以节点的形式"挂"到设备树上的,因此要想获取这个设备的属性信息,必须
先获取到这个设备的节点。Linux 内核使用 device_node 结构体来描述一个节点,此结构体定
义在文件 include/linux/of.h 中,定义如下

示例代码 24.8.1.1 device_node 节点
49 struct device_node {
50 const char *name;
/* 节点名字 */
51 const char *type;
/* 设备类型 */
52 phandle phandle;
53 const char *full_name;
/* 节点全名 */
54 struct fwnode_handle fwnode;
55
56 struct property *properties;
/* 属性 */
57 struct property *deadprops;
/* removed 属性 */
58 struct device_node *parent;
/* 父节点 */
59 struct device_node *child;
/* 子节点 */
60 struct device_node *sibling;
61 struct kobject kobj;
62 unsigned long _flags;
63 void *data;
64 #if defined(CONFIG_SPARC)
65 const char *path_component_name;
66 unsigned int unique_id;
67 struct of_irq_controller *irq_trans;
68 #endif
69 };
所以在驱动代码中需要定义一个device_node类型的变量。
/* dtsled设备结构体 */

struct dtsled_dev{

dev_t devid; /* 设备号 */

struct cdev cdev; /* cdev */

struct class *class; /* 类 */

struct device *device; /* 设备 */

int major; /* 主设备号 */

int minor; /* 次设备号 */

struct device_node *nd; /* 设备节点 */

};

static struct dtsled_dev dtsled; /* led设备 */

3.10.1 of_find_node_by_path****函数

例子:dtsled.nd = of_find_node_by_path("/led");

3.10.2 of_property_read_string****函数

例1:ret = of_property_read_string(dtsled.nd, "status", &str);
if(!ret) {
if(strcmp(str, "okay"))
return -EINVAL;
}
例2:ret = of_property_read_string(dtsled.nd, "compatible", &str);
if(0 > ret)

return -EINVAL;

if(strcmp(str, "alientek,led"))

return -EINVAL;

3.10.3 of_iomap函数

例子
static inline void led_ioremap(void)
{
data_addr = of_iomap(dtsled.nd, 0);
dirm_addr = of_iomap(dtsled.nd, 1);
outen_addr = of_iomap(dtsled.nd, 2);
intdis_addr = of_iomap(dtsled.nd, 3);
aper_clk_ctrl_addr = of_iomap(dtsled.nd, 4);
}

第4章.设备树下LED驱动实验

(1)优势:原先"在驱动文件 newchrled.c 中定义有关寄存器物理地址,然后使用 io_remap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对 GPIO 的初始化",本章我们在"新字符设备驱动实验"基础上完成,本章我 们使用设备树来向 Linux 内核传递相关的寄存器物理地址,Linux 驱动文件使用上一章讲解的 OF 函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的 IO。
(2)添加LED这个设备对应的设备树节点,打开 linux 内核源码目录下的arch/arm64/boot/dts/xilinx/system-user.dtsi 文件,在根节点"/"下创建一个名为"led"的子节点

led**{**
compatible**="alientek,led";**
status**="okay";**
default**-state="on";**
reg = <0x0 0XFF0A0044 0x0 0x4
0x0 0XFF0A0244 0x0 0x4
0x0 0XFF0A0248 0x0 0x4
0x0 0XFF0A0254 0x0 0x4
0x0 0XFF5E00AC 0x0 0x4
>;

};
因为MPsoc是64位,所以前面的0x0是补了32个0。
(3)驱动代码编写(基于newchrled.c)
添加1:在设备结构体 dtsled_dev 中添加了成员变量 nd,nd 是 device_node 结构体类型指针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加 device_node 指针变量来存放这个节点。
struct dtsled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
};
static struct dtsled_dev dtsled; /* led设备 */
修改1:通过使用 of_iomap 函数替换之前使用 ioremap 函数来实现物理地址到虚拟地址的映射,它能够直接解析给定节点的 reg 属性,并将 reg 属性中存放的物理地址和长度进行映射,使用不同的下标依次对 reg 数组中记录的不同组"物理地址-长度"地址空间进行映射。
static inline void led_ioremap(void)
{
data_addr = of_iomap(dtsled.nd, 0);
dirm_addr = of_iomap(dtsled.nd, 1);
outen_addr = of_iomap(dtsled.nd, 2);
intdis_addr = of_iomap(dtsled.nd, 3);
aper_clk_ctrl_addr = of_iomap(dtsled.nd, 4);
}
添加2:设备相关代码:
(1)通过 of_find_node_by_path 函数获取设备树根节点下的 led 节点
(2)通过 of_property_read_string 函数获取 led 节点的"status"属性的内容
(3)通过 of_property_read_string 函数获取 led 节点的"compatible"属性的内容
(4)通过 of_property_read_string 函数获取 led 节点的"default-state"属性的内容
/* 1.寄存器地址映射 */

//根据路径获取设备节点

dtsled.nd = of_find_node_by_path("/led");

if(NULL == dtsled.nd) {

printk(KERN_ERR "led node can not found!\r\n");

return -EINVAL;

}

/* 2.读取 status 属性 */

ret = of_property_read_string(dtsled.nd, "status", &str);

if(!ret) {

if(strcmp(str, "okay"))

return -EINVAL;

}

/* 3、获取 compatible 属性值并进行匹配 */

ret = of_property_read_string(dtsled.nd, "compatible", &str);

if(0 > ret)

return -EINVAL;

if(strcmp(str, "alientek,led"))

return -EINVAL;

printk(KERN_ERR "led device matching successful!\r\n");

//适应设备树中设置的初始状态

ret = of_property_read_string(dtsled.nd, "default-state", &str);

if(!ret){

if(!strcmp(str, "on"))

val |= (0x1U << 12);

else

val &= ~(0x1U << 12);

} else

val &= ~(0x1U << 12);

writel(val, data_addr);

第5章.gpio子系统介绍

(1)内核维护者在内核中设计了一些统一管控系统资源的体系(gpio子系统,input子系统等),这些体系让内核能够对系统 资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。例如系统中所有的 GPIO 就属于系统资源,每个驱动模块如果要使用某个 GPIO 就要先调用特殊的接口先申请,申请到后使用,使用完后要释放 。又譬如中断号也是一种资源,驱动在使用前也必须去 申请,只能申请成功之后才能使用,否则不能使用。
(2)gpio 子系统是 linux 内核当中用于管理 GPIO 资源的一套系统,它提供了很多 GPIO 相关的 API 接口。驱动程序中使用 GPIO 之前需要向 gpio 子系统申请,申请成功之后才可以使用,例如设置 GPIO 的输入、输出方向,设置 GPIO 输出高或低电平、读取 GPIO 输入电平等等。(本质是:寄存器开发------>库函数开发)
(3)设备树中的gpio信息
在zynqmp.dtsi,在里面找到如下所示内容:gpio 节点信息描述了 ZYNQ MPSoC 器件 PS GPIO 控制器的所有信息。

示例代码 20.1.2 gpio0 节点
648 gpio**:gpio@ff0a0000 {
649 compatible ="xlnx,zynqmp-gpio-1.0"
;**
650 status ="disabled";
651 #gpio**-cells = <0x2>;
652 interrupt
-parent = <&gic>;
653 interrupts = <0 16 4>;
654 interrupt
-controller;**
655 #interrupt**-cells = <2>;
656 reg = <0x0 0xff0a0000 0x0 0x1000>;
657 gpio
-controller;**
658 power**-domains = <&zynqmp_firmware 46>;
659 };
"#gpio-cells"属性和"#address-cells"类似,在 gpio 节点中#gpio-cells 的值等于 0x2,表示一共有两个 cell,大家可以这样理解,使用 gpio 的时候,需要传递 2 个参数过去,第一个参数为 GPIO 编号,比如"&gpio 38"就表示 GPIO MIO38。第二个参数表示GPIO 极 性 , 如 果 为0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平 有 效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。所以在 led 节点中"led-gpio = <&gpio 38 GPIO_ACTIVE_HIGH>;"就表示使用了 GPIOBank1 MIO38 这个管脚,并且是高电平有效。
gpio
-controller;表示 gpio 节点是个 GPIO 控制器,表示这个节点对应驱动程序是 gpio 驱动。
示例代码的gpio信息如下(我个人感觉下面的gpios和上文说的led-gpio是一个意思)
示例代码 20.1.1 gpio 信息
11 gpio
-led0 {
12 label ="PS_LED0"
;**
13 gpios = <&gpio 38 GPIO_ACTIVE_HIGH>;
14 linux**,default-trigger ="timer";**
15 };
......
41 gpio**-key1 {
42 label ="PS_KEY0"
;**
43 gpios = <&gpio 40 GPIO_ACTIVE_LOW>;
44 linux**,**code = <KEY_UP>;
45 };

1.gpio子系统的API函数

1.1gpio_request****函数

例子:

/* 4.获取设备树中的led-gpio属性,得到LED所使用的GPIO编号 */

gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);

if(!gpio_is_valid(gpioled.led_gpio)) {

printk(KERN_ERR "gpioled: Failed to get led-gpio\n");

return -EINVAL;

}

printk(KERN_INFO "gpioled: led-gpio num = %d\r\n", gpioled.led_gpio);

/* 5.向gpio子系统申请使用GPIO */

ret = gpio_request(gpioled.led_gpio, "LED-GPIO");

if (ret) {

printk(KERN_ERR "gpioled: Failed to request led-gpio\n");

return ret;

}

1.2 gpio_free****函数

/* 释放GPIO */

gpio_free(gpioled.led_gpio);

1.3 gpio_direction_input****函数

1.4 gpio_direction_output****函数

/* 6.将led gpio管脚设置为输出模式 */

gpio_direction_output(gpioled.led_gpio, 0);

1.5 gpio_get_value****函数

1.6 gpio_set_value****函数

/* 7.初始化LED的默认状态 */

ret = of_property_read_string(gpioled.nd, "default-state", &str);

if(!ret) {

if (!strcmp(str, "on"))

gpio_set_value(gpioled.led_gpio, 1);

else

gpio_set_value(gpioled.led_gpio, 0);

} else

gpio_set_value(gpioled.led_gpio, 0);

2.gpio相关of函数

2.1 of_get_named_gpio函数

获取设备树中的led-gpio属性,得到LED所使用的GPIO编号。

例子:gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);

第6章.gpio子系统下的LED实验

(1)我们在 dtsled.c 驱动代码中获取到了 GPIO 有关寄存器物理地址,然 后使用 of_iomap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址 完成对 GPIO 的初始化,但本质上还是跟裸机没啥区别,还非常的麻烦。所以本章就带大家 完成一个 gpio 子系统下的 led 驱动程序。

(2)本章实验重点内容如下:

1、修改第十九章创建的 led 节点,在节点中指定 led 使用的 GPIO。

2、去掉 led 节点中的 reg 属性,因为不需要用到了。
3、在第十九章驱动实验的基础上修改驱动代码,获取设备树中传入的 gpio,调用 gpio
子系统提供的接口函数对 gpio 进行操控。
(3)修改设备树:打开 linux 内核源码目录下的 arch/arm64/boot/dts/xilinx/system-user.dtsi 文件,对 led 节点内容进行修改,去掉之前 reg 属性,添加 led-gpio 节点指定 LED 所使用的 GPIO 管脚。

1 #define GPIO_ACTIVE_HIGH 0
2 #define GPIO_ACTIVE_LOW 1
......
19 led**{**
20 compatible**="alientek,led";**
21 status**="okay";**
22 default**-state="on";**
23
24 led**-**gpio = <&gpio 38 GPIO_ACTIVE_HIGH>;
25 };

(4)修改驱动代码:
第 26 行,使用 include 包含 of_gpio.h 头文件,因为我们的代码中使用到了与 gpio 相关的
OF 函数。
第 28~29 行,定义 led 设备的设备号数量以及 led 设备的名字。
第 32~41 行,定义了 struct gpioled_dev 结构体,在第十九章的基础上加入 led_gpio 用来
表示 GPIO 的编号。
第 80~98 行,使用 gpio_set_value 函数替代第十九章直接操作 GPIO 寄存器的做法,那么
这个函数我们在前面一张给大家介绍过了。
第 125~129 行,跟之前是一样的,从设备树中获取 led 节点,包括后面读取"status"属
性以及"compatible"属性。
第 153~157 行,重点在这里,使用 of_get_named_gpio 函数获取设备树中 led 节点指定的
gpio 管脚,得到一个 GPIO 编号,那么这个编号是 linux 内核 GPIO 子系统对 gpio 的编号,一
个编号就对应一个 gpio,那么我们这里得到的这个编号就对应设备树中 led-gpio 指定的那个
管脚(gpio_38---MIO38)。得到 GPIO 编号之后使用 gpio_is_valid 函数判断编号是否是有效
编号。
第 162~166 行,通过 gpio_request 函数去申请对该 gpio 管脚的使用权。
第 169 行,通过 gpio_direction_output 函数将 gpio 设置为输出模式。
第 172~179 行,根据 led 节点的"default-state"属性初始化 LED 的状态,使用
gpio_set_value 函数设置 gpio 输出高或低电平。
后面跟第十九章就是一样的,需要注意的是,第 240~256 行,驱动模块的出口函数中一
定要把申请的 gpio 管脚给释放,通过 gpio_free 函数释放 gpio。

复制代码
#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 <asm/mach/map.h> */
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#define GPIOLED_CNT     1          /* 设备号个数 */
#define GPIOLED_NAME    "gpioled"  /* 名字 */

/* dtsled设备结构体 */
struct gpioled_dev {
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int led_gpio;           /* LED所使用的GPIO编号 */
};

static struct gpioled_dev gpioled;  /* led设备 */

/*
 * @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 = &gpioled;  /* 设置私有数据 */
    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 ret;
    char kern_buf[1];

    ret = copy_from_user(kern_buf, buf, cnt);   // 得到应用层传递过来的数据
    if(0 > ret) {
        printk(KERN_ERR "kernel write failed!\r\n");
        return -EFAULT;
    }

    if (0 == kern_buf[0])
        gpio_set_value(gpioled.led_gpio, 0); // 如果传递过来的数据是0则关闭led
    else if (1 == kern_buf[0])
        gpio_set_value(gpioled.led_gpio, 1); // 如果传递过来的数据是1则点亮led

    return 0;
}

/*
 * @description         : 关闭/释放设备
 * @param -- filp        : 要关闭的设备文件(文件描述符)
 * @return              : 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .read    = led_read,
    .write   = led_write,
    .release = led_release,
};

static int __init led_init(void)
{
    const char *str;
    int ret;

    /* 1.获取led设备节点 */
    gpioled.nd = of_find_node_by_path("/led");
    if(NULL == gpioled.nd) {
        printk(KERN_ERR "gpioled: Failed to get /led node\n");
        return -EINVAL;
    }

    /* 2.读取status属性 */
    ret = of_property_read_string(gpioled.nd, "status", &str);
    if(!ret) {
        if (strcmp(str, "okay"))
            return -EINVAL;
    }

    /* 3、获取compatible属性值并进行匹配 */
    ret = of_property_read_string(gpioled.nd, "compatible", &str);
    if(0 > ret) {
        printk(KERN_ERR "gpioled: Failed to get compatible property\n");
        return ret;
    }

    if (strcmp(str, "alientek,led")) {
        printk(KERN_ERR "gpioled: Compatible match failed\n");
        return -EINVAL;
    }

    printk(KERN_INFO "gpioled: device matching successful!\r\n");

    /* 4.获取设备树中的led-gpio属性,得到LED所使用的GPIO编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if(!gpio_is_valid(gpioled.led_gpio)) {
        printk(KERN_ERR "gpioled: Failed to get led-gpio\n");
        return -EINVAL;
    }

    printk(KERN_INFO "gpioled: led-gpio num = %d\r\n", gpioled.led_gpio);

    /* 5.向gpio子系统申请使用GPIO */
    ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
    if (ret) {
        printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
        return ret;
    }

    /* 6.将led gpio管脚设置为输出模式 */
    gpio_direction_output(gpioled.led_gpio, 0);

    /* 7.初始化LED的默认状态 */
    ret = of_property_read_string(gpioled.nd, "default-state", &str);
    if(!ret) {
        if (!strcmp(str, "on"))
            gpio_set_value(gpioled.led_gpio, 1);
        else
            gpio_set_value(gpioled.led_gpio, 0);
    } else
        gpio_set_value(gpioled.led_gpio, 0);

    /* 8.注册字符设备驱动 */
     /* 创建设备号 */
    if (gpioled.major) {
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
        if (ret)
            goto out1;
    } else {
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        if (ret)
            goto out1;

        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }

    printk("gpioled: major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);

     /* 初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

     /* 添加一个cdev */
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    if (ret)
        goto out2;

     /* 创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) {
        ret = PTR_ERR(gpioled.class);
        goto out3;
    }

     /* 创建设备 */
    gpioled.device = device_create(gpioled.class, NULL,
                gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) {
        ret = PTR_ERR(gpioled.device);
        goto out4;
    }

    return 0;

out4:
    class_destroy(gpioled.class);

out3:
    cdev_del(&gpioled.cdev);

out2:
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

out1:
    gpio_free(gpioled.led_gpio);

    return ret;
}

static void __exit led_exit(void)
{
    /* 注销设备 */
    device_destroy(gpioled.class, gpioled.devid);

    /* 注销类 */
    class_destroy(gpioled.class);

    /* 删除cdev */
    cdev_del(&gpioled.cdev);

    /* 注销设备号 */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

    /* 释放GPIO */
    gpio_free(gpioled.led_gpio);
}

/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("DengTao <773904075@qq.com>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

第7章 前六章代码总结

过程:"使用老的字符注册函数+手动创建设备节点+物理地址放在驱动代码"

|

|

"使用新的字符驱动函数+使用cdev+主动创建设备节点+物理地址放在驱动代码"

|

|

"使用设备结构体+使用新的字符驱动函数+使用cdev+主动创建设备节点+物理地址放在设备树"

|

|

"除了物理地址放在设备树不同,其他同上,不定义物理地址,使用gpio子系统和相关API函数"

(1)定义设备的结构体,之中包含了需要的私有设备

/* dtsled设备结构体 */

struct gpioled_dev {

dev_t devid; /* 设备号 */

struct cdev cdev; /* cdev */

struct class *class; /* 类 */

struct device *device; /* 设备 */

int major; /* 主设备号 */

int minor; /* 次设备号 */

struct device_node *nd; /* 设备节点 */

int led_gpio; /* LED所使用的GPIO编号 */

};

static struct gpioled_dev gpioled; /* led设备 */

(2)实现open,read,write,release函数

/*

* @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 = &gpioled; /* 设置私有数据 */

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

char kern_buf1;

ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据

if(0 > ret) {

printk(KERN_ERR "kernel write failed!\r\n");

return -EFAULT;

}

if (0 == kern_buf0)

gpio_set_value(gpioled.led_gpio, 0); // 如果传递过来的数据是0则关闭led

else if (1 == kern_buf0)

gpio_set_value(gpioled.led_gpio, 1); // 如果传递过来的数据是1则点亮led

return 0;

}

/*

* @description : 关闭/释放设备

* @param -- filp : 要关闭的设备文件(文件描述符)

* @return : 0 成功;其他 失败

*/

static int led_release(struct inode *inode, struct file *filp)

{

return 0;

}

(3)定义设备操作函数

/* 设备操作函数 */

static struct file_operations gpioled_fops = {

.owner = THIS_MODULE,

.open = led_open,

.read = led_read,

.write = led_write,

.release = led_release,

};

(4)驱动模块入口

配置设备树相关:

1.获取led设备节点:gpioled.nd = of_find_node_by_path("/led");

2.读取status属性:ret = of_property_read_string(gpioled.nd, "status", &str);

3.获取compatible属性值并进行匹配:ret = of_property_read_string(gpioled.nd, "compatible", &str);

4.获取设备树中的led-gpio属性,得到LED所使用的GPIO编号:gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);

------》使用gpio子系统配置gpio

1.向gpio子系统申请使用GPIO:ret = gpio_request(gpioled.led_gpio, "LED-GPIO");

2.将led gpio管脚设置为输出模式:gpio_direction_output(gpioled.led_gpio, 0);

3.初始化LED的默认状态:

ret = of_property_read_string(gpioled.nd, "default-state", &str);

if(!ret) {

if (!strcmp(str, "on"))

gpio_set_value(gpioled.led_gpio, 1);

else

gpio_set_value(gpioled.led_gpio, 0);

} else

gpio_set_value(gpioled.led_gpio, 0);

------》创建设备号

if (gpioled.major) {

gpioled.devid = MKDEV(gpioled.major, 0);

ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);

if (ret)

goto out1;

} else {

ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);

if (ret)

goto out1;

gpioled.major = MAJOR(gpioled.devid);

gpioled.minor = MINOR(gpioled.devid);

}

------》注册cdev并添加一个cdev

gpioled.cdev.owner = THIS_MODULE;

cdev_init(&gpioled.cdev, &gpioled_fops);

ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

if (ret)

goto out2;

------》创建类和设备

gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);

gpioled.device = device_create(gpioled.class, NULL,

gpioled.devid, NULL, GPIOLED_NAME);

------》解决报错

out4:

class_destroy(gpioled.class);

out3:

cdev_del(&gpioled.cdev);

out2:

unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

out1:

gpio_free(gpioled.led_gpio);

相关推荐
知识分享小能手3 小时前
线性代数学习教程,从入门到精通,向量组的线性相关性 — 完整知识点梳理(7)
学习·线性代数·机器学习
Shell运维手记3 小时前
Linux 常用基础命令学习笔记
linux·运维·笔记·学习·算法·github
动词ing4 小时前
【学习笔记】C语言(数组指针与指针数组+字符数组+函数+参数传递+字符串作为形参+递归函数+指针函数+回调函数+结构体嵌套+内存动态分配函数)
c语言·笔记·学习
小O的算法实验室6 小时前
IEEE TII,学习为多目标深度学习生成偏好
人工智能·深度学习·学习
吃好睡好便好6 小时前
说说高温对情绪的影响
学习·生活·情绪·高温
for_ever_love__7 小时前
python基础语法学习: 文件操作
python·学习
m4Rk_7 小时前
【论文阅读】Agent 记忆机制(38):AMA——用多智能体协作动态选择记忆粒度
论文阅读·人工智能·学习
zjnlswd9 小时前
c# 刘铁猛 学习笔记
笔记·学习
LuminousCPP9 小时前
单链表专题(三)-刷题复盘篇:从快慢指针到环形链表 II 数学推导
数据结构·经验分享·笔记·学习·算法·链表