一、前言

设备树是每一个Linux驱动工程师都必须把握的一个知识点,有好多之前做单片机的同学刚接触Linux驱动时,会一脸懵!

虽然设备树的使用并没有你们想象的这么复杂,对于大部分工程师来说,只要会更改即可。

好多粉丝留言说,希望彭老师提供一个设备树到驱动解析的实例。

必须安排!

在学习设备树之前,你们一定要认清楚哪些是platform总线,请详尽学习下边这篇文章:

《手把手教Linux驱动10-platform总线解读》

关于设备树理论部份内容请学习下边这篇文章:

《手把手教linux驱动11-linux设备驱动统一模型》

关于驱动基础文章,可以去B站学习一口君的入门视频:

《从学Linux驱动入门视频》

https://www.bilibili.com/video/BV1d5411A7VJ?spm_id_from=333.999.0.0

有了这种基础知识后,我们就可以来编撰一个设备树的实例,

下边彭老师就给你们讲解怎样自己添加一个设备树节点,并怎样在驱动中提取出设备树的信息。

老规矩,代码从0开始编撰,而且全部验证通过linux设备驱动,并分享给你们。

二、测试平台

linux设备驱动_驱动设备无法启动代码10_驱动设备无法使用

本次测试在开发板上操作,操作环境如下:

1.编译环境

ubuntu 16.04

2.交叉编译工具

root@ubuntu:/home/peng/linux-3.14# arm-none-linux-gnueabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-none-linux-gnueabi-gcc
COLLECT_LTO_WRAPPER=/home/peng/toolchain/gcc-4.6.4/bin/../libexec/gcc/arm-arm1176jzfssf-linux-gnueabi/4.6.4/lto-wrapper
Target: arm-arm1176jzfssf-linux-gnueabi
………………
gcc version 4.6.4 (crosstool-NG hg+default-2685dfa9de14 - tc0002)

3.开发板

开发板:fs4412
soc:exynos4412

4.内核版本

Linux kernel 3.14.0

三、内核解析设备树通常过程系统启动后,uboot会从网路或则flash、sd卡中读取设备树文件(具体由uboot命令给出),

引导linux内核启动后,会把设备树镜像保存到的显存地址传递给Linux内核,Linux内核会解析设备树镜像,从设备树中提取硬件信息并逐一初始化。

其中设备树信息会被转换成structplatform_device类型变量。

而驱动要解析设备树,必须定义structplatform_driver类型结构体变量linux cp,并通过函数platform_driver_register()注册。

这二者就会注册到platform总线,当驱动和设备树节点匹配成功后,就调用structplatform_driver中.probe技巧。

驱动设备无法启动代码10_linux设备驱动_驱动设备无法使用

其中设备树节点会封装在structdevice_node结构体变量中各个属性信息会封装在structproperty结构体变量中,她们与structplatform_device结构体之间关系如下:

linux设备驱动_驱动设备无法使用_驱动设备无法启动代码10

四、驱动构架

以下是一口君编撰的驱动构架,

我们只须要将测试代码填充到hello_probe()中即可:

static int hello_probe(struct platform_device *pdev)
{
 printk("match ok n");
 
//解析代码编写
 return 0;
}
static  int hello_remove(struct platform_device *pdev)
{
 printk("hello_remove n");
 return 0;
}
static struct of_device_id beep_table[] = {
  {.compatible = "yikoulinux"},
};
static struct platform_driver hello_driver =
{
 .probe = hello_probe,
 .driver.name = "duang",
 .remove = hello_remove,
 .driver = {
  .name = "yikoupeng",
  .of_match_table = beep_table,
 },
};
static int hello_init(void)
{
 printk("hello_init n");
 return platform_driver_register(&hello_driver);
}
static void hello_exit(void)
{
 printk("hello_exit n");
 platform_driver_unregister(&hello_driver);
 return;
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

五、设备树节点

下边是给出的设备树信息:

 yikou_node{
        compatible = "yikoulinux";
        reg = ;
        reg-names = "peng";
        interrupt-parent=;
        interrupts =,;
    
        csm_gpios=;
        
        crl0_gpio=;
        crl1_gpio=;
        rst_gpio=;
        cfg_gpio=;
  
  phy_ref_freq = ;  /* kHz */  
  suspend_poweroff;
  
  clock-names = "xusbxti",
   "otg";
  yikou_node {
   compatible = "leadcore,dsi-panel";
   panel_name = "lcd_rd_rm67295";
   refresh_en = ;
   bits-per-pixel = ; 
  };
    };

其中包括常见reg、中断、整型值、bool值、字符串、子节点、时钟等属性。

一定要注意,好多属性的给出会由于使用的SOC平台的不同有所差别,下边介绍下GPIO和中断编撰原理:

1.GPIO

gpio信息的给出有以下两种方式:

  csm_gpios=;
 crl0_gpio=;
 crl1_gpio=;
 rst_gpio=;
 cfg_gpio=;

第1种是公用同一个名子,第2种是每一个gpio单独使用1个名子。

gpio须要指明父节点,关于gpio父节点的说明下说明文档(一般linux-3.14Documentation下有关于该内核版本的一些模块说明,很重要):

linux-3.14Documentationdevicetreebindingsgpio.txt
For example, the following could be used to describe gpios pins to use
as chip select lines; with chip selects 0, 1 and 3 populated, and chip
select 2 left empty:
 gpio1: gpio1 {
  gpio-controller
   #gpio-cells = ;
 };
 gpio2: gpio2 {
  gpio-controller
   #gpio-cells = ;
 };
 [...]
  chipsel-gpios = ,
    ,
    , /* holes are permitted, means no GPIO 2 */
    ;
Note that gpio-specifier length is controller dependent.  In the
above example, &gpio1 uses 2 cells to specify a gpio, while &gpio2
only uses one.
gpio-specifier may encode: bank, pin position inside the bank,
whether pin is open-drain and whether pin is logically inverted.
Exact meaning of each specifier cell is controller specific, and must
be documented in the device tree binding for the device.
Example of the node using GPIOs:
 node {
  gpios = ;
 };
In this example gpio-specifier is "18 0" and encodes GPIO pin number,
and empty GPIO flags as accepted by the "qe_pio_e" gpio-controller.

翻译总缔结如下几点:

gpio父节点须要包含属性

 gpio-controller、    表示是gpi控制器
 #gpio-cells = ;    表示子节点包括2个属性

对于子节点是2个属性的函数例如:

 gpios = ;

父节点是qe_pio_e其中18表示GPIOpin值,就是gpio下边管理的pin脚序号,该pin值通常就须要查询用户指南&电路图。

2.中断

中断属性节点如下:

        interrupt-parent=;
        interrupts =,;

其中

interrupt-parent=;: 该中断信号所述的中断控制器
interrupts =,;  :描述中断属性,其中中第一个值表示该中断所述中断控制器index,第二个值表示中断触发方式

驱动设备无法启动代码10_linux设备驱动_驱动设备无法使用

中断子节点格式如下:

linux-3.14Documentationdevicetreebindingsgpio.txt
Example of a peripheral using the GPIO module as an IRQ controller:
 funkyfpga@0 {
  compatible = "funky-fpga";
  ...
  interrupt-parent = ;   #父节点
  interrupts = ;     #节点属性
 };

中断子节点说明文档如下:

linux-3.14Documentationdevicetreebindingsinterrupt-controllerinterrupts.txt
  b) two cells
  ------------
  The #interrupt-cells property is set to 2 and the first cell defines the
  index of the interrupt within the controller, while the second cell is used
  to specify any of the following flags:
    - bits[3:0] trigger type and level flags
        1 = low-to-high edge triggered          上升沿
        2 = high-to-low edge triggered    下降沿
        4 = active high level-sensitive      高电平有效
        8 = active low level-sensitive          低电平有效

我们所填写的中断父节点gpx1定义如下(该文件由三星厂家出厂订制好):

linux-3.14archarmbootdtsexynos4x12-pinctrl.dtsi
  gpx1: gpx1 {
   gpio-controller;        #gpio控制器
   #gpio-cells = ;      #子节点有2个属性
   interrupt-controller;  #中断控制器
   interrupt-parent = ;    #父节点gic
   interrupts = , , , ,   #子节点属性约束
         , , , ;
   #interrupt-cells = ;
  };

可见三星的exynos4412平台中gpx1,既可以做gpio控制器又可以做中断控制器,而gpx1作为中断控制器则路由到gic上。其中interrupts属性说明如下:

linux-3.14Documentationdevicetreebindingsarmgic.txt
Main node required properties:
- compatible : should be one of:
 "arm,gic-400"
 "arm,cortex-a15-gic"
 "arm,cortex-a9-gic"
 "arm,cortex-a7-gic"
 "arm,arm11mp-gic"
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
  interrupt source.  The type shall be a  and the value shall be 3.
  The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
  interrupts.
  The 2nd cell contains the interrupt number for the interrupt type.
  SPI interrupts are in the range [0-987].  PPI interrupts are in the
  range [0-15].
  The 3rd cell is the flags, encoded as follows:
 bits[3:0] trigger type and level flags.
  1 = low-to-high edge triggered
  2 = high-to-low edge triggered
  4 = active high level-sensitive
  8 = active low level-sensitive
 bits[15:8] PPI interrupt cpu mask.  Each bit corresponds to each of
 the 8 possible cpus attached to the GIC.  A bit set to '1' indicated
 the interrupt is wired to that CPU.  Only valid for PPI interrupts.

翻译总结:

interrupts = 

第1个0表示该中断是SPI类型中断,若果是1表示PPI类型中断24表示中断号(通过查询电路图和datasheet获得)第三个0表示中断触发形式再指出一遍不同的平台gpio、中断控制器管理可能不一样linux设备驱动,所以填写方式可能会有差别,不可教条

六、驱动提取设备树信息方式

驱动解析代码与设备树节点之间关系如下,代码与属性用相同颜色框出:

linux设备驱动_驱动设备无法使用_驱动设备无法启动代码10

of开头的函数请参考《手把手教linux驱动11-linux设备驱动统一模型》

七、编译(ubuntu中操作)

驱动编译:

驱动设备无法使用_linux设备驱动_驱动设备无法启动代码10

注意,内核必须提早编译好

设备树编译:

编译设备树命令,各个厂家的SDK都不尽相同红旗linux系统,本例制做参考。

除此之外驱动模块文件、设备树文件怎么导出给开发板,差异也比较大,本文不再给出步骤。

八、加载模块(开发板上操作)

加载模块后执行结果如下:

[root@peng test]# insmod driver.ko 
[   26.880000] hello_init 
[   26.880000] match ok 
[   26.880000] mem_res1 : [0x114000a0]  mem_res2:[0x139d0000] 
[   26.885000] irq_res1 : [168]  irq_res2:[169] 
[   26.890000] mem_resp:[114000a0]
[   26.890000] 
[   26.895000] phy_ref_freq:26000
[   26.900000] suspend_poweroff [true]
[   26.900000] suspend_poweroff_test [false]
[   26.900000] 
[   26.905000] csm_gpios :[231][232][233][234]
[   26.910000] CTL0:[217] CTL1:[218] RST:[219] CFG:[216]
[   26.915000] bits_per_pixel:32
[   26.920000] panel_name:lcd_rd_rm67295
[   26.925000] refresh_en [true]

其中复印的信息就是最终我们解析出的设备树里的硬件信息,我们就可以按照这种信息进行相关资源申请、初始化。

同时设备树中的信息,会以文件节点方式创建在一下目录中:

驱动设备无法启动代码10_linux设备驱动_驱动设备无法使用

linux设备驱动_驱动设备无法启动代码10_驱动设备无法使用

本文原创地址:https://www.linuxprobe.com/sbsjlqdlsbqd.html编辑:刘遄,审核员:暂无