设备树学习总结


设备树参考

自定义设备树

设备树有什么用?

驱动是什么?驱动可以理解为一个中间软件,将底层硬件资源抽象封装并提供API给上层应用使用。

在学习STM32开发时,没有驱动这一说法,因为一般都是在一个程序中就实现了:对硬件资源的操作,并实现相应的业务逻辑。以uart的使用为例:

  1. 先在头文件中定义好uart的硬件资源信息,如引脚、波特率、寄存器等设置,FLAG-dts
  2. 编写相应的驱动函数,如uart的初始化、发送一个字节、接收一个字节等,FLAG-driver
  3. 利用驱动函数编写业务逻辑,如uart双方先定义好通信协议,然后对uart收到的数据进行解析,以执行相应的动作,FLAG-app
  4. 上面的功能实现可以认为是写在一个程序中的;

而对于嵌入式Linux驱动开发,则是通过设备树单独描述硬件资源

  1. 在设备树中通过节点对外设进行描述,描述的属性不定,但常见的有compatible, reg, pinctrl, gpio等,作用同FLAG-dts
  2. compatible的值与内核中某个驱动程序相匹配,则该驱动程序就会执行(在驱动程序中就会使用节点所描述的硬件资源,并给上层应用提供API),作用同FLAG-driver
  3. 利用驱动程序提供的API编写应用程序,作用同FLAG-app
  4. 上面的功能实现在不同的文件中:设备树、驱动程序;

设备树在哪里?

Linux源码下的arch/arm/boot/dts/

如何编译设备树

在Linux顶层文件夹下make dtbs

相关makefile

基于 ARM 架构的 SOC 有很多种,一种 SOC 又可以制作出很多款板子,每个板子都有一个对应的 DTS 文件,那么如何确定编译哪一个 DTS 文件呢?我们就以 I.MX6ULL 这款芯片对应的板子为例来看一下,打开 arch/arm/boot/dts/Makefile,有如下内容:

dtb-$(CONFIG_SOC_IMX6UL) += \
	imx6ul-14x14-ddr3-arm2.dtb \
	imx6ul-14x14-ddr3-arm2-emmc.dtb	\
	......
dtb-$(CONFIG_SOC_IMX6ULL) += \
	imx6ull-14x14-ddr3-arm2.dtb \
	......
	imx6ull-14x14-evk.dtb \
	imx6ull-14x14-evk-btwifi.dtb \
	imx6ull-14x14-evk-emmc.dtb \
	imx6ull-14x14-evk-gpmi-weim.dtb \
	imx6ull-14x14-evk-usb-certi.dtb \
	# imx6ull-alientek-emmc.dts对应的编译结果文件
	imx6ull-alientek-emmc.dtb \
	imx6ull-14x14-emmc-4.3-800x480-c.dtb \

如果我们使用 I.MX6ULL 新做了一个板子,只需要新建一个此板子对应的.dts 文件,然后将对应的.dtb 文件名添加到 dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb 文件。

如何创建自己的设备树文件

  1. 首先可以根据相似的、已有的设备树文件进行复制,如使用命令cp imx6ull-14x14-evk.dts imx6ull-alientek-emmc.dts

  2. 然后再根据自己的实际板卡对imx6ull-alientek-emmc.dts进行修改;

  3. 此时只是有了设备树文件,如需要编译,则需要将其加入到Makefile中去。在同级目录中找到Makefile,并找到dtb-$(CONFIG_SOC_IMX6ULL),添加自己的设备树文件;

    dtb-$(CONFIG_SOC_IMX6ULL) += \
    	......
    	// 将自己的设备树添加到makefile中去
    	imx6ull-alientek-emmc.dtb \
    
  4. 通过命令make dtbs即可生成imx6ull-alientek-emmc.dtb

gpio和pinctrl子系统

pinctrl

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:pin controller 节点的格式,没有统一的标准!!!!每家芯片都不一样。甚至上面的 group、function 关键字也不一定有,但是概念是有的

//client device
gpioled {
    compatible = "atk,gpioled";
    pinctrl-names = "default";  //gpioled这个设备有1个状态,
    pinctrl-0 = <&pinctrl_led>; //这是对应的状态,即在pinctrl_led那里声明了。
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay";
};

//pin ctroller(imx6ull的格式)
pinctrl_led: ledgrp {
    fsl,pins = <
    	MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0x17059  //将GPIO1_IO03复用为GPIO1_IO03
    >;
};

gpio

在几乎所有 ARM 芯片中,GPIO 都分为几组,每组中有若干个引脚。所以在使用 GPIO 子系统之前,就要先确定:它(我们要用的管脚)是哪组的?组里的哪一个?

在设备树中,“GPIO 组”就是一个 GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“gpio1”,然后指定要用它里面的哪个引脚,比如<&gpio1 0>。

定义 GPIO Controller 是芯片厂家的事,我们怎么引用某个引脚呢?在自己的设备节点中使用属性"[<name>-]gpios",示例如下:

gpioled {
    compatible = "atk,gpioled";
    pinctrl-names = "default";  //gpioled这个设备有1个状态,
    pinctrl-0 = <&pinctrl_led>; //这是对应的状态,即在pinctrl_led这里声明了。
    //gpioled这个设备使用gpio1的第3个引脚,并设为低有效
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay";
};

总结

用上面gpioled这个设备结点来说明gpio和pinctrol子系统。

pinctrl-names是说明这个结点有几个状态的,对于一个“client device”来说,比如对于一个 UART 设备,它有多个“状态”:default、sleep 等,那对应的引脚也有这些状态。怎么理解? 比如默认状态下,UART 设备是工作的,那么所用的引脚就要复用为 UART 功能。在休眠状态下,为了省电,可以把这些引脚复用为 GPIO 功能;或者直接把它们配置输出高电平。

deviceNode {
	pinctrl-names = "uart", "led";
	pinctrl-0 = <&pinctrl_uart>;
	pinctrl-1 = <&pinctrl_led>;
}
//解释如下:
这个结点有两种状态(两个名字),是父亲时,就要履行父亲的责任,所以要变成父亲的状态(pinctrl_uart)。是学生时,要做学生该做的事,要变成学生的状态(pinctrl_led)。
下图中让某个结点在某种状态下的将gpio1-3复用为gpio1-3

在这里插入图片描述

led-gpio设置某个设备用那个管脚,并默认为什么状态。

led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
#解释
gpiol这个GPIO组已经被厂商写好,我们只管用就行。名字格式为[<name>-]gpio。
其实pinctrl和gpio中都出现了具体的管脚。但led-gpio只是说用那个,而pinctrl还要设置为对应的模式。
# 此设备结点没有用到gpio子系统,只是用了pinctrol子系统来配置管脚的模式
&usdhc2 {
	pinctrl-names = "default", "state_100mhz", "state_200mhz";
	pinctrl-0 = <&pinctrl_usdhc2_8bit>;
	pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
	pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
	bus-width = <8>;
	non-removable;
	status = "okay";
};

# 状态0(默认状态)对应的配置情况
pinctrl_usdhc2_8bit: usdhc2grp_8bit {
	fsl,pins = <
			MX6UL_PAD_NAND_RE_B__USDHC2_CLK     0x10069
			MX6UL_PAD_NAND_WE_B__USDHC2_CMD     0x17059
			MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x17059
			MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x17059
			MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x17059
			MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x17059
			MX6UL_PAD_NAND_DATA04__USDHC2_DATA4 0x17059
			MX6UL_PAD_NAND_DATA05__USDHC2_DATA5 0x17059
			MX6UL_PAD_NAND_DATA06__USDHC2_DATA6 0x17059
			MX6UL_PAD_NAND_DATA07__USDHC2_DATA7 0x17059
		>;
};

我发现pinctrl都在结点iomuxc或iomuxc_snvs中。

再看一个lcd结点

&lcdif {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_lcdif_dat
		     &pinctrl_lcdif_ctrl>;
	display = <&display0>;
}
# 解释
lcdif这个设备结点有1种默认状态,这种状态它的配置情况是pinctrl-0,包含两个具体的配置情况,一个是数据接口,另一个是控制接口。
其实我觉得可以写在一起,比如:
pinctrl-0=<&pinctrl_lcdif_dat_ctrl>


# 数据管脚组的配置情况
pinctrl_lcdif_dat: lcdifdatgrp {
	fsl,pins = <
		MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x18
		MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x18
		......
		>;
}

# 控制管脚组的配置情况
pinctrl_lcdif_ctrl: lcdifctrlgrp {
	fsl,pins = <
        MX6UL_PAD_LCD_CLK__LCDIF_CLK	    0x18
        MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  0x18
        MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    0x18
        MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    0x18
	>;
};

# 合在一起
pinctrl_lcdif_dat_ctrl: lcdifdatctrlgrp {
	fsl,pins = <
		......
	>
}

gpioled驱动编写

设备树编写

alpha正点原子上的led灯在gpio1的第3个管脚上,所以在根结点下创建一个gpioled结点,如下:

/{
	gpioled {
		compatible = "cdj,led";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl-led>;
		led-gpios = <gpio1 3 GPIO_ACTIVE_LOW>;
		status = "oaky";
	};
	......
}

&iomuxc {
	pinctrl_led: ledgrp {
		fsl,pins = <
			MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0x17059
		>;
	};
	......
}

驱动程序编写

1. 驱动入口-注册平台驱动-__init中

/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",c
        .of_match_table = ask100_leds,
    },
};

//在led_init中注册
err = platform_driver_register(&chip_demo_gpio_driver); 

2. 当平台驱动和平台设备匹配成功后会调用probe函数

在probe函数中获取led的管脚描述符-struct gpio_desc

static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	/* 4.1 设备树中定义有: led-gpios=<...>;	*/
    led_gpio = gpiod_get(&pdev->dev, "led", 0);
    
	/* 4.2 注册file_operations 	*/
	major = register_chrdev(0, "100ask_led", &led_drv);

	led_class = class_create(THIS_MODULE, "100ask_led_class");
	if (IS_ERR(led_class)) {
		unregister_chrdev(major, "led");
		gpiod_put(led_gpio);
		return PTR_ERR(led_class);
	}

	device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_lecd0 */
        
    return 0;
}

3. 在open中初始化led

gpiod_direction_output(led_gpio, 0);

4. 在write中设置led

err = copy_from_user(&status, buf, 1);
/* 根据次设备号和status控制LED */
gpiod_set_value(led_gpio, status);

5. 驱动卸载时-remove函数

device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
gpiod_put(led_gpio);

6.模块退出(卸载)时-__exit函数中

我觉得吧,remove函数被调用后就会自动调用__exit函数。

platform_driver_unregister(&chip_demo_gpio_driver);
  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux系统中,设备树是一种用于描述硬件设备的数据结构。设备树是一种以树形结构组织的文本文件,它描述了系统中的各个设备、节点和设备之间的连接关系。 Linux系统使用设备树来识别和加载硬件设备驱动程序。设备树描述了系统的硬件拓扑结构,包括处理器、内存、总线、外设等。每个设备都在设备树中有一个节点来表示。 在设备树上挂载一个设备,首先需要了解设备树中相应设备的节点。设备树节点包含设备的属性、注册信息和与其他节点的连接关系。通常,设备节点的名字与设备驱动程序的名字相对应。 挂载设备的过程包括以下步骤: 1. 找到设备树中相应设备的节点。 2. 确认设备节点的属性和连接关系是否正确。 3. 如果设备节点不存在,需要添加设备节点到设备树中。 4. 加载设备驱动程序并执行相应的初始化操作。 5. 根据设备节点的属性和连接关系,配置设备的资源和参数。 6. 最后,设备被成功挂载在设备树上。 通过这个过程,Linux系统能够准确地识别和管理硬件设备,并与之进行交互。设备树的使用使得系统具有更好的可移植性和扩展性,使得设备驱动程序的开发和维护更加方便。同时,通过设备树的描述,系统的硬件拓扑结构也更加清晰可见。 ### 回答2: 在Linux系统中,设备树(Device Tree)被广泛用于描述系统中的硬件组件和设备的连接方式、配置参数等信息。在设备树中,每个设备被表示为一个节点(node),节点之间的父子关系表示设备之间的连接关系。 要在设备树上挂载设备,首先需要对设备进行描述。这包括指定设备的类型、地址、驱动程序等相关信息。然后,在设备树的适当位置创建一个新的节点,用于描述该设备。 设备树中的节点包括属性(properties)和子节点(child nodes)。属性用于描述设备的特性和配置参数,子节点用于描述设备的子设备或者连接关系。 针对每个设备,可以定义一个设备树驱动程序(Device Tree Driver),用于从设备树中读取设备的配置信息,并将其与相应的驱动程序关联起来。设备树驱动程序在系统启动时被加载,它可以根据设备树中的描述,完成设备的初始化和配置。 在设备树中挂载设备的过程可分为以下几个步骤: 1. 编写设备树描述文件,描述设备的类型、地址、配置参数等信息。 2. 将设备树描述文件编译成二进制格式(.dtb文件)。 3. 在系统引导过程中,Bootloader加载设备树文件到内存中,并将其传递给内核。 4. 内核根据设备树中描述的信息,加载对应的设备驱动程序,并完成设备的初始化和配置。 5. 应用程序可以通过设备文件或者系统调用等方式,与设备进行交互和通信。 通过在设备树上挂载设备,可以实现硬件设备的自动检测和配置,简化了系统的驱动开发和维护工作,提高了系统的可移植性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值