从设备树中获得相关的属性值

本文介绍了如何在Linux内核中利用设备树获取寄存器地址,创建设备节点,以及如何编写设备驱动程序,如IMX6ULL平台上的EMMC设备,通过设备树属性初始化GPIO控制LED。

1. 概述

使用设备树来向linux内核传递相关的寄存器的物理地址,linux驱动文件使用of函数,从设备树获得所需要的属性值,然后使用获得的属性值来进行初始化相关的IO

    1.在imx6ull-alientek-emmc.dts 文件中创建线管的设备节点

    2.编写设备驱动,从设备树中获得相关的属性值

    3.使用从设备树获得的有关属性值来初始化LED所使用的IO

2. 设备树节点 

alphaled {
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "atkalpha-led";
            status = "okay";
            reg = <  0X020C406C 0X04     /* CCM_CCGR1_BASE */
                     0X020E0068 0X04     /* SW_MUX_GPIO1_IO03_BASE */
                     0X020E02F4 0X04     /* SW_PAD_GPIO1_IO03_BASE */
                     0X0209C000 0X04     /* GPIO1_DR_BASE */
                     0X0209C004 0X04 >;   /* GPIO1_GDIR_BASE */
        };

3. 代码 dts_led.c


#define DTS_LED_CNT   1          /*设备个数*/
#define DTS_LED_NAME  "dts_led"  /*名字*/

#define LEDON   0 
#define LEDOFF  1 

/* 映射后的寄存器虚拟地址指针 */
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;

struct dts_led_dev{
    dev_t dev;                  /*设备号*/
    struct cdev cdev;           /*cdev*/
    struct class *class ; 
    struct device *device;      /*设备*/
    int major   ;               /*主设备号*/
    int minor   ;               /*次设备号*/
    struct device_node *nd ;    /*设备节点*/
} ; 

struct dts_led_dev dts_led ;  /*led 设备*/

static void led_switch(u8 state)
{
    u32 val = 0 ; 
    if(state == LEDON)
    {
        val = readl(GPIO1_DR) ; 
        val &= ~(1 << 3) ; 
        wiretel(val ,GPIO1_DR) ;
    }
    elseif(state == LEDOFF)
    {
        val  = readl(GPIO1_DR) ; 
        val |=(1 << 3) ; 
        wiretel(val ,GPIO1_DR) ;
    }
}
/*
* @description:打开设备
* @param-inode:传递给驱动的inode
* @param-filp :设备文件,file结构体有个叫private_data的成员变量 
                   一般在open的时候将private_data指向设备结构体
* @return     :0 成功;其他 失败
*/
static int dts_led_open(struct inode *inode , struct file *filp)
{
    filp->private_data = &dts_led ; 
    return 0 ; 
}
/*
* @description:从设备读取数据
* @param-filp : 要打开的设备驱动文件
* @param-buf  : 返回给用户空间的数据缓冲区
* @param-count: 要读取的数据长度
* @param-offt : 相对于文件首地址的偏移
* @return     : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t dts_led_read(struct file *filp , char __user *buf , size_t count
                            , loff_t *offt)
{
    return 0 ; 
}
/*
* @description: 向设备写数据
* @param-filp :设备驱动文件,表示打开的文件描述符
* @param-buf  :要写入的数据
* @param-count:要写入的数据长度
* @param-offt :相对于文件首地址的偏移
* @return     :写入的字节数,如果为负值,表示写入失败
*/

static ssize_t dts_led_write(struct file *filp , char __user *buf , size_t count
                            , loff_t *offt)
{   
    int value = 0 ;
    unsigned char databuf[1] ; 
    unsigned led_state ; 
    value = copy_from_user(databuf,buf,count) ; 
    if(value< 0 )
    {
        printk("kernel write failed! \r\n")
        return -EFAULT ; 
    }
    led_state = databuf[0] ; /*获取状态值*/

    if(led_state == LEDON)
    {
        led_switch(LEDON) ;
    }
    elseif(led_state == LEDOFF)
    {
        led_switch(LEDOFF) ;
    }
    return 0 ; 
}
/*
* @description: 关闭释放文件
* @param-inode:传递给驱动的inode
* @param-filp :设备驱动文件,表示打开的文件描述符
* @return     :0 成功 负数表示失败
*/
static int dts_led_release(struct inode *inode , struct file *filp)
{
    return 0 ; 
}

/*设备操作结构体*/
struct file_operations dts_led_fops = {
    .owner      = THIS_MODULE,
    .open       = dts_led_open,
    .write      = dts_led_write,
    .read       = dts_led_read,
    .release    = dts_led_release,
};
/*
* @description  : 模块入口函数
* @param        : none
* @return       : none
*/
static int __init led_init(void)
{
     u32 val = 0 ; 
     int ret ;
     u32 reg_data[14] ; 
     const char *str ; 
     struct property *proper ;

    /*获取设备树的属性数据*/
    /*1. 获取设备节点: alphaled*/
    dts_led.nd = of_find_node_by_path("/alphaled");
    if(dts_led.nd == NULL)
    {
        printk("alphaled node not found !  \r\n") ;
        return -EINVAL ; 
    }
    else{
        printk("alphaled node has been found ! \r\n ") ; 
        
    }

    /*2.获取compatible 属性内容*/
    proper = of_find_property(des_led.nd , "compatible" , NULL) ; 
    if(proper == NULL)
    {
        printk("compatible property find failed \r\n") ; 
    }else{
        printk("compatible = %s\r\n",(char * )proper->value) ; 

    }

    /*3.获取status属性的内容*/
    ret = of_property_read_string(dts_led.nd , "status" , &str) ;
    if(ret < 0)
    {
        printk("status read failed \r\n") ; 
    }else 
    {
        printk("status = %s\r\n", str) ;
    }

    /*4.获取reg属性的内容*/ 
    ret = of_property_read_u32_array(dts_led.nd , "reg" , reg_data, 10) ; 
    if(ret <0 ) 
    {
        printk("reg read failed \r\n") ; 
    }else
    {
        u8 i = 0 ;
        printk("reg data: \r\n");
        for(i ; i < 10 ; i++)
        {
            printk("%#x" ,reg_data[i]);
        }printk("\r\n") ; 

    }
    /*初始化LED*/
    /*1.寄存器地址映射*/
    #if 0 
    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) ;          
    #else 
    IMX6U_CCM_CCGR1  = of_iomap(dts_led.nd , 0 ) ;  
    SW_MUX_GPIO1_IO03= of_iomap(dts_led.nd , 1 ) ;  
    SW_PAD_GPIO1_IO03= of_iomap(dts_led.nd , 2 ) ;  
    GPIO1_DR         = of_iomap(dts_led.nd , 3 ) ;  
    GPIO1_GDIR       = of_iomap(dts_led.nd , 4 ) ;   
    #endif 

    /*2.时能时钟*/
    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) ;

    /*4.寄存器SW_PAD_GPIO1_IO03设置为IO属性*/
    writel(0x10B0,SW_PAD_GPIO1_IO03) ;

    /*5.设置GPIO1_IO03为输出功能*/
    val = readl(GPIO1_GDIR) ; 
    val &= ~(1 << 3 ) ; 
    val |= (1 << 3 ) ; 
    writel(val,GPIO1_GDIR) ;

    /*6.默认关闭LED*/
    val = readl(GPIO1_DR) ;
    val |= (1 << 3) ;
    writel(val,GPIO1_DR) ;


    /*注册字符设备驱动*/
    /*1.创建设备号*/
    if(dts_led.major) /*如果定义了设备号*/
    {
        dts_led.devid = MKDEV(dts_led.major , 0) ;
        register_chrdev_region(dts_led.devid , 
        DTS_LED_CNT , DTS_LED_NAME) ;
    }
    else
    {
        alloc_chrdev_region(&dts_led.devid , 0 , DTS_LED_CNT
        , DTS_LED_NAME) ; /*申请设备号*/
        dts_led.major = MAJOR(dts_led.devid) ;/*获得分配号的主设备号*/
        dts_led.minor = MINOR(dts_led.devid) ;/*获得分配号的次设备号*/
    }
    printk("dts_led major = %d , minor = %d \r\n",dts_led.major
    ,  dts_led.minor) ; 

    /*2.初始化cdev*/
    dts_led.cdev.owner = THIS_MODULE ;
    cdev_init(&dts_led.cdev ,  dts_led.devid , DTS_LED_CNT) ; 
    /*3.添加一个cdev*/
    cdev_add(dts_led.cdev , dts_led.devid , DTS_LED_CNT) ;

    /*4.创建类*/
    dts_led.class = class_create(THIS_MODULE , DTS_LED_NAME) ;
    if(IS_ERR(dts_led.class))
    {
        return PTR_ERR(dts_led.class) ; 
    }

    /*5.创建设备*/
    nwdchrled.device = device_create(dts_led.class ,NULL, 
        dts_led.devid , NULL , DTS_LED_NAME) ;
    if(IS_ERR(nwdchrled.device))
    {
        return PTR_ERR(dts_led.device) ; 
    }
}

/*
* @description  : 模块卸载函数
* @param-none   : none
* @return       : none
*/
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(&dts_led.cdev) ;  /*删除cdev*/
    unregister_chrdev_region(dts_led.devid , DTS_LED_CNT) ;/*注销设备号*/

    device_destroy(dts_led.class , dts_led.devid) ; 
    class_destroy(dts_led.class) ;
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

<think>嗯,用户现在专注于设备树中的reg属性,这已经是第三次深入询问设备树相关知识了。之前讨论过设备树的基本概念和节点结构,这次用户明显需要reg属性的技术细节。用户可能是嵌入式Linux开发者,正在调试硬件驱动,遇到寄存器映射问题才会如此执着于reg属性的具体用法。 从引用内容看,用户提供的资料质量很高,直接来自Linux内核文档级别的说明。特别注意到用户贴的示例中包含了寄存器基地址、长度、中断号等关键信息,说明用户正在处理实际硬件驱动开发。不过用户没提具体芯片平台,可能需要补充跨架构的注意事项。 reg属性确实值得深挖:一方面它是硬件与驱动衔接的核心,另一方面实际使用中容易混淆地址类型转换问题。需要重点解释清楚以下几点: 1)reg属性与CPU地址空间的关系——特别是那些带地址转换的复杂场景(比如PCI设备) 2)64位地址的处理方式(用户给的示例都是32位) 3)数组形式的多区域注册(用户提供的引用[3]只展示单区域) 4)配套属性#address-cells/#size-cells的联动机制 用户上次提问时已经理解了设备树节点结构,这次可以跳过基础定义,直接切入高级应用。需要强调几个易错点:地址单位对齐问题(比如某些总线要求4字节对齐)、big-endian转换、虚拟地址映射的注意事项。不过暂时不用展开reg-names,避免信息过载。 注意到用户给的示例里用了十六进制地址,但没提数据类型。实际开发中常混淆物理地址和总线地址,这点必须明确区分。另外用户可能遇到多级总线的情况(比如通过I2C连接的设备),这类嵌套reg的处理需要特别说明。 最后要给出调试建议——毕竟用户很可能是为了解决实际问题。比如如何通过/sys/firmware/devicetree确认reg解析是否正确,以及驱动中如何使用platform_get_resource获取reg信息。不过这些内容可以放在“应用示例”环节,保持主流程清晰。 </think>好的,我们来详细介绍一下 Linux 设备树(Device Tree)中的 `reg` 属性。它是描述设备硬件资源,特别是**内存映射寄存器(MMIO)区域**的核心属性。 --- ### `reg` 属性的核心作用 `reg` 属性用于指定设备在其父总线地址空间内**所占用的物理地址范围**。通常,这些地址范围对应于设备的**寄存器组**或**内存缓冲区**,驱动程序需要通过读写这些地址来与硬件设备通信[^1][^2][^3]。 --- ### `reg` 属性的语法格式 ```dts reg = <address1 length1 [address2 length2 ...]>; ``` * **`< >`:** 值列表必须包含在尖括号内。 * **地址 (`addressN`):** 表示**寄存器区域的起始物理地址**(在父总线的地址空间中)。 * **长度 (`lengthN`):** 表示该**寄存器区域的大小**(以字节为单位)。 * **多区域:** 一个设备可能有多个独立的寄存器区域(例如,控制寄存器组、数据缓冲区、状态寄存器等)。`reg` 属性可以包含多个 `地址 + 长度` 对来定义这些区域[^1][^2][^3]。 --- ### 关键依赖属性:`#address-cells` 和 `#size-cells` `reg` 属性的含义严重依赖于其**父节点**(或祖先节点)定义的两个属性: 1. **`#address-cells`:** 定义了需要用多少个 **32位整数(cell)** 来表示一个 **起始地址**。 2. **`#size-cells`:** 定义了需要用多少个 **32位整数(cell)** 来表示一个 **区域长度**。 #### 常见配置示例 * **简单 SoC 内存映射 (最常见的32位系统):** ```dts soc { #address-cells = <1>; // 地址用1个cell表示 (32位地址) #size-cells = <1>; // 长度用1个cell表示 (32位长度) ... uart0: serial@101f0000 { compatible = "ns16550a"; reg = <0x101f0000 0x1000>; // 起始地址 = 0x101F0000, 长度 = 0x1000 (4KB) ... }; }; ``` 这里,父节点 `soc` 定义了 `#address-cells = <1>` 和 `#size-cells = <1>`。因此,子节点 `uart0` 的 `reg` 属性包含 **2个整数**:第一个是地址 (0x101F0000),第二个是长度 (0x1000)[^3]。 * **64位地址系统:** ```dts soc { #address-cells = <2>; // 地址需要2个cell表示 (64位地址) #size-cells = <2>; // 长度需要2个cell表示 (64位长度) ... bigmem: memory@100000000 { device_type = "memory"; reg = <0x1 0x00000000 0x0 0x80000000>; // 起始地址 = 0x100000000, 长度 = 0x80000000 (2GB) }; }; ``` 父节点定义了 `#address-cells = <2>` 和 `#size-cells = <2>`。子节点 `bigmem` 的 `reg` 属性包含 **4个整数**:前两个组成64位地址 (0x00000001\_00000000 = 0x100000000),后两个组成64位长度 (0x00000000\_80000000 = 0x80000000)。 * **多个寄存器区域:** ```dts parent-bus { #address-cells = <1>; #size-cells = <1>; ... multi-reg-device@a0000000 { compatible = "vendor,multi-reg"; reg = <0xa0000000 0x1000 // 区域1: 地址 0xA0000000, 长度 4KB 0xa0008000 0x200 // 区域2: 地址 0xA0008000, 长度 512B 0xa000c000 0x1000>; // 区域3: 地址 0xA000C000, 长度 4KB ... }; }; ``` 设备 `multi-reg-device` 有三个独立的寄存器区域。 --- ### 驱动程序中如何访问 `reg` 属性 Linux 内核驱动程序通常使用 **Platform Device/Driver** 机制来访问设备树中定义的资源: 1. **平台设备注册:** 内核在启动时解析设备树(DTB 文件),为匹配的节点创建 `struct platform_device` 对象。这个对象包含解析出的资源信息(包括 `reg` 属性)。 2. **资源获取:** 在驱动程序的 `probe` 函数中,使用 `platform_get_resource()` 或 `platform_get_resource_byname()`(如果配合 `reg-names` 属性)获取 `reg` 属性定义的资源。 3. **内存映射:** 驱动程序获得物理地址和长度后,需要使用 `devm_ioremap_resource()` 等函数将其映射到内核的虚拟地址空间,之后才能通过指针读写这些寄存器。 ```c /* 在驱动的probe函数中 */ struct resource *res; void __iomem *base_addr; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取第一个MEM资源 (对应reg的第一个区域) if (!res) { dev_err(&pdev->dev, "Memory resource not found\n"); return -EINVAL; } base_addr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base_addr)) { return PTR_ERR(base_addr); } /* 现在可以通过 base_addr (虚拟地址) 访问寄存器 */ writel(0x55, base_addr + REG_CTRL_OFFSET); // 写寄存器 value = readl(base_addr + REG_STATUS_OFFSET); // 读寄存器 ``` --- ### `reg-names` 属性 (可选) * **作用:** 为 `reg` 属性中定义的各个区域提供**有意义的名称字符串**。这使得驱动程序可以通过名称(而非索引)来请求特定的资源区域,增强了代码的可读性和可维护性。 * **格式:** 是一个字符串列表,每个字符串对应 `reg` 属性中的一个区域。 ```dts ethernet@4a100000 { compatible = "vendor,nic"; reg = <0x4a100000 0x1000 // 控制寄存器 0x4a101000 0x2000 // 发送缓冲区 0x4a103000 0x2000>; // 接收缓冲区 reg-names = "control", "tx_buf", "rx_buf"; }; ``` * **驱动中使用:** ```c struct resource *res_tx; res_tx = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tx_buf"); ``` --- ### 总结关键点 1. **核心功能:** `reg` 属性定义了设备在其父总线地址空间中的**物理地址范围**,主要用于 MMIO 寄存器或内存缓冲区。 2. **格式依赖:** `reg` 属性的格式(每个地址和长度占用的 cell 数量)由父节点的 `#address-cells` 和 `#size-cells` **严格定义**。 3. **多区域支持:** 一个 `reg` 属性可以包含多个 `地址+长度` 对,描述设备的多个独立资源区域。 4. **驱动访问:** 驱动程序通过平台设备机制获取 `reg` 资源,并使用 IO 内存映射函数(`ioremap`, `devm_ioremap_resource`)将其转换为可访问的虚拟地址。 5. **辅助属性:** `reg-names` 属性(可选)为 `reg` 的各个区域提供名称,方便驱动程序按名称查找资源。 理解 `reg` 属性是编写和调试基于设备树Linux 驱动程序的基础,它直接关系到驱动如何找到并操作硬件寄存器[^1][^2][^3]。 --- **相关问题** 1. `#address-cells` 和 `#size-cells` 属性的具体规则是什么?它们是如何在设备树层次结构中继承的? 2. 对于连接到非内存映射总线(如 I2C、SPI)的设备,`reg` 属性的含义和使用方式有什么不同? 3. 在驱动程序中,`devm_ioremap_resource()` 和 `ioremap()` 有什么区别?为什么推荐使用前者? 4. `reg` 属性定义的物理地址与 CPU 看到的物理地址总是相同的吗?什么时候需要地址转换(例如 Ranges 属性)? 5. 如何使用 `reg-names` 属性在驱动程序中更安全地获取特定的寄存器区域?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值