嵌入式Linux设备树驱动开发 - dtsof驱动

嵌入式Linux设备树驱动开发 - dtsof驱动

一、项目概述

本项目实现了一个基于设备树(Device Tree)的驱动程序,用于解析IMX6ULL开发板的设备树信息。该驱动程序主要演示了如何在Linux内核模块中访问和解析设备树中的属性信息。

二、驱动程序分析 (dtsof.c)

1. 头文件与宏定义

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>          // 设备树相关头文件
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>       // kmalloc/kfree相关

// 模块初始化和退出函数声明
static int __init dtsof_init(void);
static void __exit dtsof_exit(void);

2. 设备树解析函数

模块初始化函数
static int __init dtsof_init(void)
{
    int ret = 0;
    struct device_node *bl_nd = NULL;   // 设备节点指针
    struct property *compprop = NULL;   // 属性指针
    const char *status;                // 状态属性值
    u32 def_value = 0;                 // 默认亮度值
    u32 elemsize = 0;                  // 数组元素个数
    u32 *brival;                        // 亮度值数组指针

    /* 查找backlight节点 */
    bl_nd = of_find_node_by_path("/backlight");
    if (bl_nd == NULL)
    {
        ret = -ENODEV;
        goto fail_findnode;
    }
    
    /* 查找compatible属性 */
    compprop = of_find_property(bl_nd, "compatible", NULL);
    if (compprop == NULL)
    {
        ret = -ENOENT;
        goto fail_findprop;
    }
    else
    {
        printk(KERN_INFO "Compatible property found: %s\r\n", (char *)compprop->value);
    }

    /* 读取status属性 */
    ret = of_property_read_string(bl_nd, "status", &status);
    if (ret < 0) {
        goto fail_findprop;
    } else {
        printk(KERN_INFO "Status property value: %s\n", status);
    }

    /* 读取default-brightness-level属性 */
    ret = of_property_read_u32(bl_nd, "default-brightness-level", &def_value);
    if (ret < 0) {
        goto fail_findprop;
    } else {
        printk(KERN_INFO "defult-brightness-level property value: %u\n", def_value);
    }

    /* 获取brightness-levels属性元素个数 */
    elemsize = of_property_count_elems_of_size(bl_nd, "brightness-levels", sizeof(u32));
    if (elemsize < 0) {
        goto fail_findprop;
    } else {
        printk(KERN_INFO "Number of brightness-level elements: %d\n", elemsize);
    }

    /* 分配内存用于存储亮度值数组 */
    brival = kmalloc(elemsize * sizeof(u32), GFP_KERNEL);
    if (!brival) {
        ret = -ENOMEM;
        goto fail_kmalloc;
    }

    /* 读取brightness-levels属性数组 */
    ret = of_property_read_u32_array(bl_nd, "brightness-levels", brival, elemsize);
    if (ret < 0) {
        goto fail_findprop;
    } else {
        int i;
        printk(KERN_INFO "Brightness levels: ");
        for (i = 0; i < elemsize; i++) {
            printk(KERN_CONT "%u ", brival[i]);
        }
        printk(KERN_CONT "\n");
    }
    
    kfree(brival);  // 释放内存
    return 0;

fail_kmalloc:
    kfree(brival);

fail_findprop:

fail_findnode:
    return ret;
}
模块退出函数
static void __exit dtsof_exit(void)
{
    printk(KERN_INFO "dtsof module exited\n");
}

3. 模块注册

module_init(dtsof_init);
module_exit(dtsof_exit);

4. 模块信息

MODULE_LICENSE("GPL");
MODULE_AUTHOR("alientek");

三、设备树文件分析 (imx6ull-alientek-emmc.dts)

1. 设备树基本结构

/dts-v1/;

#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"

/ {
	model = "Freescale i.MX6 ULL 14x14 EVK Board";
	compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
	
	/* 标准输出配置 */
	chosen {
		stdout-path = &uart1;
	};
	
	/* 内存配置 */
	memory {
		reg = <0x80000000 0x20000000>;
	};
	
	/* 保留内存区域 */
	reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;
		
		linux,cma {
			compatible = "shared-dma-pool";
			reusable;
			size = <0x14000000>;
			linux,cma-default;
		};
	};
	
	/* 背光配置节点 */
	backlight {
		compatible = "pwm-backlight";
		pwms = <&pwm1 0 5000000>;
		brightness-levels = <0 4 8 16 32 64 128 255>;
		default-brightness-level = <6>;
		status = "okay";
	};
	
	/* 其他外设配置... */
};

2. 重要配置节点

backlight节点
backlight {
	compatible = "pwm-backlight";
	pwms = <&pwm1 0 5000000>;  // 使用PWM1,周期5000000ns
	brightness-levels = <0 4 8 16 32 64 128 255>;  // 亮度等级
	default-brightness-level = <6>;  // 默认亮度等级
	status = "okay";  // 状态启用
};
pwm1配置
&pwm1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_pwm1>;
	status = "okay";
};
pinctrl_pwm1配置
pinctrl_pwm1: pwm1grp {
	fsl,pins = <
		MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
	>;
};

四、Makefile分析

KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)

obj-m := dtsof.o  # 编译成内核模块

build : kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modules  # 进入内核目录编译模块

clean:
	$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean  # 清理编译结果

五、设备树基础理论

1. 设备树基本概念

设备树(Device Tree)是一个描述硬件配置的数据结构,它将硬件信息从内核代码中分离出来,使得同一个内核可以支持多种硬件平台。

2. 设备树基本格式

  • 节点: 用斜杠斜杠包裹的路径表示,如/backlight
  • 属性: 每个节点可以包含多个属性,属性值可以是字符串、32位整数或它们的数组
  • 兼容性: compatible属性用于匹配驱动程序
  • 状态: status属性表示设备状态,"okay"表示启用

3. 常用设备树属性

  • compatible: 驱动匹配字符串
  • reg: 寄存器地址范围
  • interrupts: 中断配置
  • clocks: 时钟配置
  • pwms: PWM配置
  • brightness-levels: 亮度等级数组
  • default-brightness-level: 默认亮度等级
  • gpios: GPIO配置

六、设备树操作函数详解

1. of_find_node_by_path

struct device_node *of_find_node_by_path(const char *path);

功能:通过路径查找设备树节点
参数:path - 设备树节点路径
返回值:成功返回设备节点指针,失败返回NULL

2. of_find_property

struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);

功能:查找设备节点的属性
参数:

  • np - 设备节点指针
  • name - 属性名称
  • lenp - 返回属性值长度
    返回值:成功返回属性指针,失败返回NULL

3. of_property_read_string

int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);

功能:读取字符串类型的属性值
参数:

  • np - 设备节点指针
  • propname - 属性名称
  • out_string - 输出的字符串指针
    返回值:成功返回0,失败返回错误码

4. of_property_read_u32

int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);

功能:读取u32类型的属性值
参数:

  • np - 设备节点指针
  • propname - 属性名称
  • out_value - 输出的u32值
    返回值:成功返回0,失败返回错误码

5. of_property_count_elems_of_size

int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);

功能:计算属性值中元素的数量
参数:

  • np - 设备节点指针
  • propname - 属性名称
  • elem_size - 元素大小
    返回值:成功返回元素数量,失败返回错误码

6. of_property_read_u32_array

int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t count);

功能:读取u32数组类型的属性值
参数:

  • np - 设备节点指针
  • propname - 属性名称
  • out_values - 输出的u32数组
  • count - 要读取的元素个数
    返回值:成功返回0,失败返回错误码

七、驱动编译与测试流程

1. 驱动编译

make -C /path/to/kernel/source M=$(pwd) modules

2. 加载驱动

insmod dtsof.ko

3. 查看内核日志

dmesg | grep "dtsof"

4. 卸载驱动

rmmod dtsof.ko

八、设备树在嵌入式Linux中的作用

1. 硬件描述

设备树通过结构化的方式描述硬件平台的硬件配置,包括:

  • 外设类型和型号
  • 寄存器地址空间
  • 中断配置
  • 时钟配置
  • 外设参数配置

2. 驱动匹配

通过compatible属性,Linux内核可以将驱动程序与设备树中的设备节点进行匹配。

3. 资源分配

设备树提供了一种机制,让驱动程序可以查询硬件资源,如:

  • 内存地址
  • 中断号
  • 时钟源
  • GPIO引脚

4. 平台抽象

设备树使得Linux内核可以与硬件平台解耦,同一个内核可以支持多种不同的硬件平台。

九、设备树驱动开发要点

1. 错误处理

  • 检查设备节点和属性是否为空
  • 处理内存分配失败情况
  • 返回合适的错误码
  • 使用goto进行错误清理

2. 内存管理

  • 使用kmalloc/kfree进行动态内存分配
  • 确保所有分配的内存都被正确释放
  • 使用合适的内存分配标志(GFP_KERNEL)

3. 日志输出

  • 使用printk输出调试信息
  • 使用合适的日志级别(KERN_INFO, KERN_CONT等)
  • 提供详细的属性解析信息

4. 错误码使用

  • 使用标准Linux错误码(-ENODEV, -ENOENT, -ENOMEM等)
  • 确保错误码正确传递
  • 在模块加载时返回合适的错误码

Giee 源码仓库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值