一、前言设备树是每个Linux驱动工程师必须掌握的知识点。很多以前是单片机的朋友,刚接触Linux驱动的时候都会一头雾水!其实设备树的使用并没有大家想象的那么复杂。对于大多数工程师来说,只要能修改就可以了。很多粉丝留言说希望彭老师能提供一个设备树的例子来分析驱动。必须安排!在学习设备树之前,大家一定要搞清楚什么是平台总线。请详细研究以下文章:《手把手教Linux驱动10-platform总线详解》设备树的理论部分请研究以下文章:《手把手教linux驱动11-linux设备驱动统一模型》关于驱动基础篇,可以去B站学习一口君的介绍视频:《从学Linux驱动入门视频》https://www.bilibili.com/video/BV1d5411A7VJ?spm_id_from=333.999.0.0有了这些基础知识,我们就可以写一个设备树了,比如彭老师给大家讲解一下如何添加一个设备树节点你自己,以及如何在驱动程序中提取设备树的信息。按照老规矩,代码从0开始写,全部验证通过,分享给大家。二、测试平台本次测试在开发板上运行,运行环境如下:1、编译环境ubuntu16.042。交叉编译工具root@ubuntu:/home/peng/linux-3.14#arm-none-linux-gnueabi-gcc-vUsingbuilt-inspecs.COLLECT_GCC=arm-none-linux-gnueabi-gccCOLLECT_LTO_WRAPPER=/home/peng/toolchain/gcc-4.6.4/bin/../libexec/gcc/arm-arm1176jzfssf-linux-gnueabi/4.6.4/lto-wrapperTarget:arm-arm1176jzfssf-linux-gnueabi………………gccversion4.6.4(crosstool-NGhg+default-2685dfa9de14-tc0002)3.开发板开发板:fs4412soc:exynos44124。内核版本Linuxkernel3.14.0三、内核解析设备树的大致流程系统启动后,uboot会从网络或flash或SD卡中读取设备树文件(具体由uboot命令给定),启动Linux后kernel,它会把保存设备树映像到内存地址传给Linux内核,Linux内核会解析设备树映像,从设备树中提取硬件信息并一一初始化。设备树信息会被转换成一个structplatform_device类型的变量。驱动要解析设备树,必须定义一个structplatform_driver类型的结构体变量,并通过函数platform_driver_register()注册。两者都将注册到平台总线。当驱动和设备树节点匹配成功后,会调用structplatform_driver中的.probe方法。其中,设备树节点会被封装在structdevice_node结构体变量中,各个属性信息会被封装在structproperty结构体变量中。它们与structplatform_device结构体的关系如下:4.驱动架构下面是一口君写的驱动架构。我们只需要将测试代码填入hello_probe()即可:");return0;}staticstructof_device_idbeep_table[]={{.compatible="yikoulinux"},};staticstructplatform_driverhello_driver={.probe=hello_probe,.driver.name="duang",.remove=hello_remove,.driver={.name="yikoupeng",.of_match_table=beep_table,},};staticinthello_init(void){printk("hello_init\n");returnplatform_driver_register(&hello_driver);}staticvoidhello_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=<0x114000a00x40x139D00000x20>;reg-names="peng";interrupt-parent=<&gpx1>;interrupts=<12>,<22>;csm_gpios=<&gpx230&gpx240&gpx250&gpx260>;crl0_gpio15p;crl0_gpio15rst_gpio=<&gpx070>;cfg_gpio=<&gpx040>;phy_ref_freq=<26000>;/*kHz*/suspend_poweroff;clock-names="xusbxti","otg";yikou_node{compatible="leadcore,dsi-panel";panel_name="lcd_rd_rm67295";refresh_en=<1>;bits-per-pixel=<32>;};};包括commonreg,interrupt,integervalue,boolvalue,string,childnode,clock等一定要付费注意属性,很多属性的设置会因为使用的SOC平台不同而不同,下面介绍GPIO和中断写入的原理:1.GPIOgpio信息的给定有两种方式:csm_gpios=<&gpx230&gpx240&gpx250&gpx260>;crl0_gpio=<&gpx050>;crl1_gpio=<&gpx060>;rst_gpio=<&gpx070>;cfg_gpio=<&gpx040>;第一种是共享相同的名称,第二种是每个gpio使用单一名称。gpio需要指定父节点,gpio父节点的描述在文档中有解释(通常linux-3.14\Documentation下会有一些关于内核版本的模块描述,很重要):linux-3.14\Documentation\devicetree\bindings\gpio.txt例如,以下可用于描述gpiospin用作芯片选择线;使用芯片选择0、1和3填充,以及芯片选择2lefttempty:gpio1:gpio1{gpio-controller#gpio-cells=<2>;};gpio2:gpio2{gpio-controller#gpio-cells=[<1>;}chipsel-gpios=<&gpio1120>,<&gpio1130>,<0>,/*holesarepermitted,meansnoGPIO2*/<&gpio22>;注意gpio-specifierlength是controllerdependent.Intheaboveexample,&gpio1uses2cellstospecifyagpio,while&gpio2onlyusesone.gpio-specifier可能编码:bank,pinpositioninsidethebank,pinisopen-drain和whetherpinislogicallyinverted。每个说明符的确切含义是特定于控制器的,并且必须记录在设备的设备树绑定中。使用GPIO的节点示例:node{gpios=<&qe_pio_e180>;};在这个例子中gpio-specifier是“180”并且编码GPIO引脚号和空GPIO标志被“qe_pio_e”gpio控制器接受。翻译总结如下:gpio父节点需要包含属性gpio-controller,表示是gpi控制器#gpio-cells=<2>;表示该子节点包括2个属性,为子节点是两个属性的函数,如:gpios=<&qe_pio_e180>;父节点为qe_pio_e,其中18代表GPIO管脚值,也就是gpio下管理的管脚号。一般引脚值需要查阅用户手册&电路图2.中断中断属性节点如下:interrupt-parent=<&gpx1>;interrupts=<12>,<22>;其中interrupt-parent=<&gpx1>;:中断信号中描述的中断控制器interrupts=<12>,<22>;:描述中断属性,其中<>中的第一个值表示中断的中断控制器索引,第二个值表示中断触发方式。中断子节点格式如下:linux-3.14\Documentation\devicetree\bindings\gpio.txtExampleofaperipheralusingtheGPIOmoduleasanIRQcontroller:funkyfpga@0{compatible="funky-fpga";...interrupt-parent=<&gpio1>;#parent节点中断=<43>;#节点属性};中断子节点描述文件如下:linux-3.14\Documentation\devicetree\bindings\interrupt-controller\interrupts.txtb)twocells------------#interrupt-cellspropertyissetto2,第一个cell定义了controller中interrupt的index,而secondcell用于指定以下任意一个flagstrigsvellow=typeto-highedgettriggeredrisingedge2=high-to-lowedgettriggeredfallingedge4=activehighlevel-sensitivehigh-levelactive8=activelowlevel-sensitivelow-levelactive我们填写的中断父节点gpx1定义如下(这个文件是自定义的三星制造商OK):linux-3.14\arch\arm\boot\dts\exynos4x12-pinctrl.dtsigpx1:gpx1{gpio-controller;#gpiocontroller#gpio-cells=<2>;#childnodehas2attributesinterrupt-controller;#interruptcontrollerinterrupt-parent=<&gic>;#parentnodegicinterrupts=<0240>,<0250>,<0260>,<0270>,#子节点属性约束<0280>,<0290>,<0300>,<0310>;#interrupt-cells=<2>;};可见三星的exynos4412平台,gpx1既可以作为gpio控制器也可以作为中断控制器,gpx1作为中断控制器路由到gic其中中断属性说明如下:linux-3.14\Documentation\devicetree\bindings\arm\gic.txtMainnoderequiredproperties:-compatible:shouldbeoneof:"arm,gic-400""arm,cortex-a15-gic""arm,cortex-a9-gic""arm,cortex-a7-gic""arm,arm11mp-gic"-interrupt-controller:Identifiesthenodeasaninterruptcontroller-#interrupt-cells:Specifiesthenumberofcellsneededtoencodeaninterruptsource.Thetypeshallbeaandthevalueshallbe3.The1stcellistheinterrupttype;0forSPIinterrupts,1forPPIinterrupts.The2ndcellcontainstheinterruptnumberfortheinterrupttype.SPIinterruptsareintherange[0-987].PPI中断在范围内[0-15]。第3个单元列出标志,编码如下:位[3:0]触发类型和级别标志。1=低到高触发2=高到低触发4=活动高级别敏感8=活动低级别敏感位[15:8]PPI中断cpumask。每个位对应于连接到GIC的8个可能的CPU中的每一个。A位设置为“1”表示中断连接到该CPU。仅有效idforPPI中断。翻译总结:interrupts=<0240>第一个0表示中断是SPI类型中断,如果是1表示是PPI类型中断,24表示中断号(通过查询电路图和datasheet获得),以及第三个0表示中断触发方式。强调一下,不同平台对gpio和中断控制器的管理可能不同,所以填充方式可能不同,不教条。6、从驱动中提取设备树信息的方法出彩框:of开头的函数请参考《手把手教linux驱动11-linux设备驱动统一模型》7。编译(在ubuntu中操作)驱动编译:注意内核一定要提前编译好。实例制作参考另外,如何将驱动模块文件和设备树文件导入开发板的方法也大不相同,本文不再给出步骤。8、加载模块(在开发板上运行)加载模块后,执行结果如下:[root@pengtest]#insmoddriver.ko[26.880000]hello_init[26.880000]matchok[26.880000]mem_res1:[0x114000a0]mem_res2:[0x139D0000][26.885000]IRQ_RES1:[168]IRQ_RES2:[169][26.890000]mem_resp:[114000a0][26.890000][26.890000][26.895000]][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.9250_en00]ref[true]打印出来的信息就是我们最后分析出来的devicetree中的硬件信息,我们可以根据这些信息来申请和初始化相关资源。同时,设备树中的信息会以文件节点的形式创建在以下目录中: