Tag Archives: device tree

使用BBB的SPI

SPI是可以全双工通信的一种串行总线,两个设备之间双向通信的话一般使用3根线:SCLK,MISO,MOSI,多个设备之间双向通信的话,每个设备还需要再加上一根地址线CSn。相比之下I2C只能半双工,而且一般需要上拉电阻,但无论几个设备,都只需要2根线。更多基础知识请谷歌百度。

Beaglebone Black使用的AM3359芯片上有两个SPI,但SPI1连接到了板子的HDMI芯片上,所以除非禁用HDMI,否则我们只能使用SPI0。本文将利用自带的spidev驱动使能SPI0,并进行一下简单的验证。

1、配置device tree

首先我们用我在《使用BBB的I2C》这篇文章中使用的方法检验一下SPI相关的引脚功能是否配置正确。检查结果是,不正确,也就是说SPI默认是没有启用的,新版arm linux配置硬件的方式是利用device tree,所以我们必须要配置一个device tree来启用它。我们先到 /lib/firmware 目录中看看有没有现成的device tree source (.dts)文件可供使用。我们发现有一个BB-SPI0-00A0.dts。内容如下

/dts-v1/;  
/plugin/;  
  
/ {  
    compatible = "ti,beaglebone", "ti,beaglebone-black";  
  
    /* identification */  
    part-number = "BB-SPI0";  
    version = "00A0";  
  
    /* state the resources this cape uses */  
    exclusive-use =  
        /* the pin header uses */  
        "P9.17",    /* spi0_cs0 */  
        "P9.18",    /* spi0_d1 */  
        "P9.21",    /* spi0_d0 */  
        "P9.22",    /* spi0_sclk */  
        /* the hardware ip uses */  
        "spi0";  
  
    fragment@0 {  
        target = <&am33xx_pinmux>;  
        __overlay__ {  
            /* default state has all gpios released and mode set to uart1 */  
            bb_spi0_pins: pinmux_bb_spi0_pins {  
                pinctrl-single,pins = <  
                    0x150 0x30  /* spi0_sclk.spi0_sclk, INPUT_PULLUP | MODE0 */  
                    0x154 0x30  /* spi0_d0.spi0_d0, INPUT_PULLUP | MODE0 */  
                    0x158 0x10  /* spi0_d1.spi0_d1, OUTPUT_PULLUP | MODE0 */  
                    0x15c 0x10  /* spi0_cs0.spi0_cs0, OUTPUT_PULLUP | MODE0 */  
                >;  
            };  
        };  
    };  
  
    fragment@1 {  
        target = <&spi0>; /* spi0 is numbered correctly */  
        __overlay__ {  
            status = "okay";  
            pinctrl-names = "default";  
            pinctrl-0 = <&bb_spi0_pins>;  
  
            #address-cells = <1>;  
            #size-cells = <0>;  
  
            /* add any spi devices connected here */  
            /* note that you can do direct SPI via spidev now */  
  
            // commented out example of an adafruit 1.8" TFT display  
            // from firmare/capes/cape-bone-adafruit-lcd-00A0.dts  
            // lcd@0 {  
            //  #address-cells = <1>;  
            //  #size-cells = <0>;  
            //  
            //  compatible = "adafruit,tft-lcd-1.8-red", "sitronix,st7735";  
            //  reg = <0>;  
            //  
            //  spi-max-frequency = <8000000>;  
            //  spi-cpol;  
            //  spi-cpha;  
            //  
            //  pinctrl-names = "default";  
            //  pinctrl-0 = <&bone_adafruit_lcd_pins>;  
            //  
            //  st7735-rst = <&gpio4 19 0>;  
            //  st7735-dc = <&gpio4 21 0>;  
            // };  
        };  
    };  
};

从这个文件里我们能得到很多信息(我在此唠叨两句,也算跟大家分享一下我学习的过程),首先我们从exclusive-use这一部分能看出来AM3359芯片对SPI引脚的命名是跟一般不太一样的,它没用MISO和MOSI,而是D0和D1。通过查询4000页手册我们得知,原来是因为这两个引脚的功能是可以通过配置寄存器来互换的。默认的对应方式是d0对应miso,d1对应mosi。

再接着看,发现有一句注释

/* note that you can do direct SPI via spider now */


这个spidev就是我们要用的spi驱动,然后谷歌一下它的用法就可以了。再下面有一些被注释掉的东西,是要根据不同设备来替换的。

(以下操作都在Beaglebone上进行)

我们把自带的文件复制一份,保存为 BB-SPI0-01-00A0.dts ,然后增加一个节点,内容如下(就是原文件中注释部分要替换的内容)

spidev@0 {  
       spi-max-frequency = <24000000>;  
       reg = <0>;  
       compatible = "linux,spidev";  
};  


保存以后编译这个dts文件

# dtc -O dtb -o BB-SPI0-01-00A0.dtbo -b 0 -@ BB-SPI0-01-00A0.dts

然后把生成的.dtbo文件放到/lib/firmware目录中

# cp BB-SPI0-01-00A0.dtbo /lib/firmware/

然后把它“插”到“插槽”中(请看我的这篇博文

# echo BB-SPI0-01 > /sys/devices/bone_capemgr.*/slots

OK,这时我们进入/dev目录中就会发现比原来多了一个设备 spidev1.0 ,说明device tree配置没有问题,该设备已成功加载。

2、使用SPI

因为我手边没有SPI设备,所以我把D0和D1也就是P9.18和P9.21这两个引脚连接起来进行自发自收,如果收到了发送的数据即成功。时钟线就不必管了,因为自己跟自己的时钟肯定是同步的。

测试程序使用的是linux自带的一个spidev_test.c程序(下载地址在这里,不过还是建议直接把整个kernel下载下来比较方便搜索查询)。这个程序的内容就是发送一串16进制数,然后 printf 接收到的内容(不知道这串数有没有什么别的含义)。

下面把这个文件传到Beaglebone上,用gcc编译一下,生成可执行文件spidev_test。假设现在就在这个文件的目录下,那么我们输入

# ./spidev_test -D /dev/spidev1.0

来进行测试。得到输出

spi mode: 0  
bits per word: 8  
max speed: 500000 Hz (500 KHz)  
  
FF FF FF FF FF FF   
40 00 00 00 00 95   
FF FF FF FF FF FF   
FF FF FF FF FF FF   
FF FF FF FF FF FF   
DE AD BE EF BA AD   
F0 0D  


说明测试成功了。否则会输出一串FF。

3、为什么dts文件要那样改?

刚刚我在自带的BB-SPI0-00A0.dts文件中加了一个节点,然后向其中加了几个属性,SPI0就能用了。增加一个节点还能够理解,但为什么要加这几个属性?这个问题我想了几天也没想得很清楚。不过我知道的是,这3个属性缺一不可。

其中compatible属性是每个节点必须有的,它的作用是将这个设备和某个驱动进行绑定。比如这里就是将这个spi设备与 linux -> spidev 这个驱动绑定。我把逗号换成了箭头,是因为我觉得其实这个逗号表达的是从属关系,用箭头更合适。但是,我在kernel文件中翻遍了也没找到哪里有“linux,spidev”这样的字眼。spidev驱动倒是找到了,而且在这个驱动文件中发现了如下内容

static const struct of_device_id spidev_dt_ids[] = {  
    { .compatible = "rohm,dh2228fv" },  
    {},  
};  


我试着把BB-SPI0-01-00A0.dts里的 compatible 值换成 “rohm,dh2228fv”,结果居然也成功了!这似乎说明以后如果我们知道要用哪个驱动的话,到驱动文件里搜索compatible找到相应内容就可以了。不过,我遗憾地发现大部分驱动文件里都没有这个属性。可能只有一些硬件外设的驱动,或者是别的公司做的驱动里才会有。所以,我又迷惘了……

分析一个.dts文件

本文的正确性严重有待检验!!!

我先阅读了/lib/firmware/目录中的所有.dts文件,看一下它写了哪些东西,有哪些共性。经过阅读之后我有一些发现,记录一下。(因为这部分跟驱动关联很大,而我还不熟悉驱动相关的东西,所以只能是望文生义,不敢保证理解正确。)

以BB-UART1-00A0.dts文件为例说明

/dts-v1/;
/plugin/;

/ {
	compatible = "ti,beaglebone", "ti,beaglebone-black";

	/* identification */
	part-number = "BB-UART1";
	version = "00A0";

	/* state the resources this cape uses */
	exclusive-use =
		/* the pin header uses */
		"P9.24",	/* uart1_txd */
		"P9.26",	/* uart1_rxd */
		/* the hardware ip uses */
		"uart1";

	fragment@0 {
		target = <&am33xx_pinmux>;
		__overlay__ {
			bb_uart1_pins: pinmux_bb_uart1_pins {
				pinctrl-single,pins = <
					0x184 0x20 /* P9.24 uart1_txd.uart1_txd  OUTPUT  */
					0x180 0x20 /* P9.26 uart1_rxd.uart1_rxd  INPUT  */
				>;
			};
		};
	};

	fragment@1 {
		target = <&uart2>;	/* really uart1 */
		__overlay__ {
			status = "okay";
			pinctrl-names = "default";
			pinctrl-0 = <&bb_uart1_pins>;
		};
	};
};


所有的.dts文件中每个fragment下面紧接着就是 target 声明,然后是 __overlay__ ,这应该是device tree overlay的标准写法,从直观上看就是要重定义这个target的某些属性或添加某些属性。那我们能修改的target有哪些呢?经过查找,发现它们藏在了这里
# ls /proc/device-tree/__symbols__/

aes                         dcdc2_reg  gpmc       mmc2         timer7
am33xx_pinmux               dcdc3_reg  i2c0       mmc3         tps
baseboard_beaglebone        ecap0      i2c0_pins  name	       tscadc
baseboard_beaglebone_black  ecap1      i2c1       cop          uart1
baseboard_eeprom            ecap2      i2c2       pruss        uart2
cape_eeprom0                edma       i2c2_pins  rstctl       uart3
cape_eeprom1                ehrpwm0    intc       rstctl_pins  uart4
cape_eeprom2                ehrpwm1    lcdc       sham	       uart5
cape_eeprom3                ehrpwm2    ldo1_reg   spi0	       uart6
cpsw_emac0                  epwmss0    ldo2_reg   spi1	       usb_otg_hs
cpsw_emac1                  epwmss1    ldo3_reg   timer1       userled_pins
cpu                         epwmss2    ldo4_reg   timer2       vmmcsd_fixed
davinci_mdio                gpio1      mac        timer3       wdt2
dcan0                       gpio2      mcasp0	  timer4
dcan1                       gpio3      mcasp1	  timer5
dcdc1_reg                   gpio4      mmc1       timer6


这些都是文件,如果你用cat命令查看其中的内容,里面写的是相应target的位置。比如
# cat uart2

/ocp/serial@48022000


这个位置不是linux文件系统中的位置,而是device tree中的位置,第一个斜杠/是device tree的根节点。实际上device tree的根节点对应的正是/proc/device-tree/这个目录。所以/ocp/serial@48022000实际上对应的是/proc/device-tree/ocp/serial@48022000这个位置,我们看看这个目录里面有什么
# ls -l /proc/device-tree/ocp/serial@48022000

-r--r--r-- 1 root root  4 Jan  1 00:52 clock-frequency
-r--r--r-- 1 root root 14 Jan  1 00:52 compatible
-r--r--r-- 1 root root  4 Jan  1 00:52 interrupts
-r--r--r-- 1 root root  4 Jan  1 00:52 linux,phandle
-r--r--r-- 1 root root  7 Jan  1 00:52 name
-r--r--r-- 1 root root  4 Jan  1 00:52 phandle
-r--r--r-- 1 root root  8 Jan  1 00:52 reg
-r--r--r-- 1 root root  9 Jan  1 00:52 status
-r--r--r-- 1 root root  6 Jan  1 00:52 ti,hwmods


这些文件应该就是这个节点默认的属性了。在BB-UART1-00A0.dts里,它只修改了其中一个属性,status = “okay”; 代表使能。

	fragment@1 {
		target = <&uart2>;	/* really uart1 */
		__overlay__ {
			status = "okay";
			pinctrl-names = "default";
			pinctrl-0 = <&bb_uart1_pins>;
		};
	};


那下面两行pinctrl-names和pinctrl-0是什么呢?是添加了两个属性,定义了uart2的引脚复用。而具体复用了哪些引脚的哪些功能,是由第一个fragment定义的(可以看到pinctrl-0 = ;这句引用了上一个fragment中定义的节点)。所以接下来我们看下第一个fragment

	fragment@0 {
		target = <&am33xx_pinmux>;
		__overlay__ {
			bb_uart1_pins: pinmux_bb_uart1_pins {
				pinctrl-single,pins = <
					0x184 0x20 /* P9.24 uart1_txd.uart1_txd  OUTPUT  */
					0x180 0x20 /* P9.26 uart1_rxd.uart1_rxd  INPUT  */
				>;
			};
		};
	};


它的target是am33xx_pinmux,跟上面一样,我们先到/proc/device-tree/__symbols__/里找到am33xx_pinmux,然后cat一下内容,确定其位置是在/proc/device-tree/pinmux@44e10800,然后看一下这个目录里有什么(下面的列表里去掉了目录,只保留了文件)
# ls -l /proc/device-tree/pinmux@44e10800

-r--r--r-- 1 root root  4 Jan  1 02:24 #address-cells
-r--r--r-- 1 root root  4 Jan  1 02:24 #size-cells
-r--r--r-- 1 root root 15 Jan  1 02:24 compatible
-r--r--r-- 1 root root  4 Jan  1 02:24 linux,phandle
-r--r--r-- 1 root root  7 Jan  1 02:24 name
-r--r--r-- 1 root root  4 Jan  1 02:24 phandle
-r--r--r-- 1 root root  4 Jan  1 02:24 pinctrl-0
-r--r--r-- 1 root root  8 Jan  1 02:24 pinctrl-names
-r--r--r-- 1 root root  4 Jan  1 02:24 pinctrl-single,function-mask
-r--r--r-- 1 root root  4 Jan  1 02:24 pinctrl-single,register-width
-r--r--r-- 1 root root  8 Jan  1 02:24 reg


看起来fragment里出现的pinctrl-single,pins跟pinctrl-single,function-mask很像。经查询pinctrl-single驱动的官方说明,有很多pinctrl-single,whatever这样的属性。其中pinctrl-single,pins用来定义引脚的复用功能。下面两个16进制数,左边的代表引脚地址的偏移量,右边的代表功能。下面解释一下这两个数字是怎么来的。

首先要分清楚引脚名($PINS)和板子引脚编号(Head_pin)的区别,前者是系统对各个引脚的编号,后者是板子上引脚的位置编号。系统中可以轻松查看每个引脚的信息
root@beaglebone:~# cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pins

registered pins: 142
pin 0 (44e10800) 00000031 pinctrl-single 
pin 1 (44e10804) 00000031 pinctrl-single 
pin 2 (44e10808) 00000031 pinctrl-single 
pin 3 (44e1080c) 00000031 pinctrl-single 
pin 4 (44e10810) 00000031 pinctrl-single  
. . .
pin 137 (44e10a24) 00000028 pinctrl-single 
pin 138 (44e10a28) 00000028 pinctrl-single 
pin 139 (44e10a2c) 00000028 pinctrl-single 
pin 140 (44e10a30) 00000028 pinctrl-single 
pin 141 (44e10a34) 00000020 pinctrl-single 


这里的编号就是$PINS,编号后面括号里的内容就是这个引脚功能的地址,再后面跟着的就是引脚功能的16进制表示(实际上是AM335x芯片内部的CONTROL_MODULE寄存器地址,详见TI公司的那本4000多页的手册,我在之前的日志里提到过)。实际上在.dts文件的pinctrl-single驱动中,并没有使用完整的地址,而是用了偏移地址,基准是pin0的地址,即44e10800。那每个芯片引脚对应到哪个板子的引脚呢?Derekmolloy老师为我们总结了一份PDF文档,里面写得很清楚,大家可以到这里去看一看。

说完地址,再说功能。
第2,1,0位表示引脚的复用功能0到7,从Derekmolloy总结的PDF中能轻松查到。
第3位是引脚上拉/下拉使能,0为使能
第4位是设置上拉/下拉,0为下拉,1为上拉
第5位是输入使能,1为既能输入也能输出,0为只能输出
第6位是slew的速度,0是高速,1是低速(这个不知道是什么东西)
再高位就没用了。

我们用上面的例子解释一下,
0x184 0x20 /* P9.24 uart1_txd.uart1_txd OUTPUT */
其中0x184是偏移地址,加上0x44e10800是0x44e10984,查到是第97个芯片引脚
# pin 97 (44e10984) 00000037 pinctrl-single
查PDF知对应到板子上是P9.24这个引脚。
功能寄存器的值是0x37,化成2进制低7位是0110111,对应着功能7(查PDF知是uart1_txd.uart1_txd),使能了内部上拉,使能了输入。
解释完毕。

那为什么要创建bb_uart1_pins子节点呢?我猜纯粹是为了下面能引用。这个写法bb_uart1_pins: pinmux_bb_uart1_pins中冒号是什么含义?我找遍google也没找到,难道也是为了引用方便起的别名??我能随便起名吗?

还有我为什么能在uart2这个target中随意添加pinctrl-names和pinctrl-0这两个属性?那我还能不能添加别的属性?这个问题也暂时没找到答案。不过据我观察所有涉及引脚的功能节点都是这样写的,暂时就当成一种规则吧。更大的问题是,如果我添加了一些别的属性,系统怎么能知道我的这个属性是在哪里定义的?要用哪个驱动?似乎系统的驱动都放在了/sys/bus/platform/drivers目录下,那这些驱动都定义了哪些变量或属性呢?

还有波特率,奇偶校验啥的能在这里定义吗??

总之,BB-UART1-00A0.dts这个文件中写了两个节点的overlay,第1个节点修改了两个引脚的功能,第2个节点使能了uart1然后引用了第1个节点的引脚修改。

附:因为这两个地址太常用了,我们也可以把它们存成系统环境变量
export SLOTS=/sys/devices/bone_capemgr.8/slots
export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins
以后用$SLOTS和$PINS就可以代表这两个地址了,但注意仅对本次启动有效。

Q&A2 – 进一步理解和使用device tree

提几个关于device tree overlay的具体使用问题,作为接下来的着力点。

  1. 各种硬件资源的名字和属性都有哪些?芯片引脚地址和功能定义到哪找?(换句话说,.dts文件该怎么修改?)
  2. 加载overlay之后该怎么使用某个外设?(比如uart的模式、波特率怎么设定?输入输出文件怎么设定?)
  3. DT overlay到底能管哪些方面的东西?在硬件接口方面它是万能的吗?
  4. 如何设置系统启动就自动加载某个DT overlay?

================================================================

回答问题前先扯几句。我们知道beagleboard官网上有一些官方的硬件外设,比如lcd显示屏之类的,他们管这些外设叫做cape。其实这里是我理解狭隘了,应该说只要是修改了芯片引脚功能,或占用了空闲的引脚的东西,都可以叫做cape。比如之前我们提到的开启某些引脚的AD转换功能,其实也是给设备添加了一个virtual cape。Beaglebone Black中用一个叫做capemgr的软件管理所有的cape,不论它是实实在在的扩展板,还是虚拟的cape。这个软件的目录是

/sys/devices/bone_capemgr.*/(这里的*是一个每次系统启动可能会不一样的数字(与启动顺序有关))

如果你看过我的前一篇博客,也许还会记得我们加载device tree overlay时打开了一个文件,正是这个目录下的slots文件。slots文件就是capemgr这个软件的对外接口。slot这个单词是“插槽”的意思,看,很形象吧!我要插上一个cape,就向这个“插槽”里echo相应的设备。echo这个命令的含义是“向标准设备输出”嘛。另外,.dtbo文件只有放到/lib/firmware/目录下才能被使用。

还记得我们第一次打开slots文件看到了什么吗?
# cat /sys/devices/bone_capemgr.*/slots

0: 54:PF--- 
1: 55:PF--- 
2: 56:PF--- 
3: 57:PF--- 
4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-L Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI


这里前4项为什么是空的呢?它们是给那些有EEPROM的实体cape预留的位置。这里有一段解释,我还理解得不太好,就先把原文附上吧

Slots 0-3 get assigned by EEPROM IDs on the capes. There are 4 possible addresses for the EEPROMs (typically determined by switches on the boards) enabling up to 4 boards to be stacked, depending on what functions they use. Additional slots are “virtual”, added incrementally and are triggered in other ways.

4和5这两项则是系统已经加载的两个virtual cape,因为eMMC和HDMI确实需要占用一些引脚,所以根据前面的定义,它们也是cape。

其实编写device tree overlay某种程度上来说就是在开发驱动(前提是驱动或驱动模块已经存在于内核中),只不过相比从前那种重新编译内核的方式来说,这种方式实在太方便了。

================================================================
因为我发现前面提的问题都挺大的,所以决定另开日志进行解答,这篇日志就到此吧。

用Device tree overlay掌控Beaglebone Black的硬件资源

经过一晚上的Google,终于大致明白device tree是怎么用的了,这里简单梳理一下思路。

一、简介
===============================================================
device tree是ARM linux 3.7开始使用的系统控制硬件资源的方式,这里说的硬件资源既包括片上的诸如GPIO、PWM、I2C、ADC等资源,也包括外部拓展的如FLASH、LCD等。ARM使用device tree目前还是很新的东西(2012年低诞生的),自带3.8版本Linux系统的Beaglebone Black是第一批使用device tree的ARM设备之一。难怪目前关于它的中文资料还很少。之前的linux如果要配置硬件,需要重新编译内核,但用device tree就不必重新编译内核,甚至不必重启系统就能实现。如此方便的工具让我们来看一看庐山面目!

从单片机、STM32过渡到Cortex-A8,之前直接操作寄存器来控制硬件的思路已经不好使了,但是device tree提供了一种很类似直接操作寄存器,但是比它更有条理,更容易理解的方式。首先需要编写一个.dts文件(device tree source),在文件中说明我要设置的硬件和它的各种属性,然后使用dtc命令编译这个.dts文件生成对应的二进制文件.dtb(device tree blob),系统启动时就会加载这个device tree并配置各种硬件资源。实际上Beaglebone Black自带系统中/boot/目录下已经包含了一些编译好的.dtb文件,从文件名来看似乎每个.dtb文件都能配置一款beagleboard.org的开发板,其中有一个叫做am335x-boneblack.dtb的文件,没猜错的话应当负责了Beaglebone black的缺省硬件配置。但因为已经编译成了二进制文件,所以我们无法读取其内容。

那么我们如果想要自己修改某些功能改怎么办呢?我们肯定不能重新编译一个am335x-boneblack.dtb代替原来的文件,那样会疯掉的。不过我们可以使用device tree overlay来动态重定义某些功能。device tree overlay与device tree类似,同样是编写一个.dts文件,编译成.dtbo文件(末尾的o应该代表overlay)。不同的是我们不把它放到/boot/目录中去,它也不必在启动时加载,而可以在需要时随时进行动态加载。另外device tree overlay的.dts文件跟device tree的.dts文件格式还是有一点区别的,下面要介绍的是device tree overlay的.dts。接下来我们上机操作一下。

二、编写.dts文件
===============================================================
用ssh连接好Beaglebone black以后,我们先来找找Angstrom系统自带的.dts文件,看看它们长什么样子。用下面的命令搜索一下dts结尾的文件

# find / -name *dts

会得到下面这个列表(部分省略)

/lib/firmware/cape-bone-dvi-00A0.dts
/lib/firmware/bone_pwm_P8_45-00A0.dts
/lib/firmware/BB-SPI1A1-00A0.dts
/lib/firmware/BB-ADC-00A0.dts
/lib/firmware/BB-I2C1A1-00A0.dts
/lib/firmware/BB-BONE-SERL-01-00A1.dts
/lib/firmware/cape-bone-dvi-00A2.dts
/lib/firmware/bone_pwm_P8_13-00A0.dts
/lib/firmware/cape-bone-hexy-00A0.dts
/lib/firmware/BB-BONE-LCD7-01-00A2.dts
...


我们发现它们都在同一个目录内,/lib/firmware/,事实上系统自带的dts文件确实全部都在这个目录中,从文件名上我们会发现这里几乎包含了所有Beaglebone硬件资源的overlay,也包含了一些官方硬件外设(如lcd屏等,它们管自己的外设叫做cape)的overlay,因此以后有需要就可以直接到这里找了。下面随便打开其中一个看看(BB-UART1-00A0.dts)

/*
 * Copyright (C) 2013 CircuitCo
 *
 * Virtual cape for UART1 on connector pins P9.24 P9.26
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
/dts-v1/;
/plugin/;

/ {
   	compatible = "ti,beaglebone", "ti,beaglebone-black";

        /* identification */
        part-number = "BB-UART1";
        version = "00A0";

        /* state the resources this cape uses */
        exclusive-use =
                /* the pin header uses */
                "P9.24",        /* uart1_txd */
                "P9.26",        /* uart1_rxd */
                /* the hardware ip uses */
                "uart1";

        fragment@0 {
                target = <&am33xx_pinmux>;
                __overlay__ {
                        bb_uart1_pins: pinmux_bb_uart1_pins {
                                pinctrl-single,pins = <
                                        0x184 0x20 /* P9.24 uart1_txd.uart1_txd MODE0 OUTPUT (TX) */
                                        0x180 0x20 /* P9.26 uart1_rxd.uart1_rxd MODE0 INPUT (RX) */
                                >;
                        };
                };
        };

        fragment@1 {
                target = <&uart2>;	/* really uart1 */
                __overlay__ {
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <&bb_uart1_pins>;
                };
        };
};


它的语法跟c语言有点类似。我先从中抽掉不重要的内容,把它写成下面的伪代码

/ {
        fragment@0 {
                target = <&am33xx_pinmux>;
                __overlay__ {
                        bb_uart1_pins: pinmux_bb_uart1_pins {
                                pinctrl-single,pins = <
                                        0x184 0x20 /* P9.24 uart1_txd.uart1_txd MODE0 OUTPUT (TX) */
                                        0x180 0x20 /* P9.26 uart1_rxd.uart1_rxd MODE0 INPUT (RX) */
                                >;
                        };
                };
        };

        fragment@1 {
                target = <&uart2>;
                __overlay__ {
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <&bb_uart1_pins>;
                };
        };
};


从这里就能看出.dts文件的结构了——是一个树形结构。第一行的/代表根,下面的fragment@0fragment@1是其两个分支节点。每个fragment节点下面又各有一个__overlay__节点(这些节点的名字都是固定的)。每个fragment节点下面相邻的target说明这个节点要修改的对象,在__overlay__节点下面的内容阐明了要修改的属性。

具体来说,am33xx_pinmux可以定义芯片功能复用引脚的具体功能,它使用了pinctrl-single,pins这个驱动,其中第一项0x184代表要修改的引脚,第二项0x20代表要修改成哪个功能(pinctrl-single的具体用法见这里)。这里把P9.24和P9.26两个引脚定义成了uart1的TX和RX。uart2这个target则使能了uart1(这个uart2实际上对应的是硬件的uart1)。

如果把树形结构什么的都忽略掉,就会发现其实它实现了我之前用寄存器干的事:定义引脚功能,然后使能串口。

那么当我想自己写device tree的时候,一上来就会遇到一个问题:我怎么知道我想控制的对象target名字是什么?我怎么知道它有哪些属性?取值范围是什么?答案在linux官网上。(不过有一些在这里似乎找不到,回头解决)

了解了dts文件的基本框架,我们再把之前丢掉的细节拿回来说明一下。(这些细节有些是非常重要的,实际使用中一定不要随意丢掉!)
首先这两行说明了dts的版本号,声明了这个文件的内容是一个plugin

/dts-v1/;
/plugin/;


根节点下面的一行说明了它的适用平台,这个是必须要写的。

compatible = "ti,beaglebone", "ti,beaglebone-black";


接下来的部分说明了这个device tree overlay的名字和版本号(版本号似乎只能是00A0)

/* identification */
part-number = "BB-UART1";
version = "00A0";


再下面的部分说明了要使用的引脚和硬件设备

/* state the resources this cape uses */
        exclusive-use =
                /* the pin header uses */
                "P9.24",        /* uart1_txd */
                "P9.26",        /* uart1_rxd */
                /* the hardware ip uses */
                "uart1";


接下来就是device tree overlay的具体内容,前面已经简单解释过了,但似乎还是看不太明白,也写不出来。实际上我们并不需要自己从头开始写,因为在系统/lib/firmware/目录中已经自带了很多.dts文件,我们只需要在它们的基础上进行修改就行了。需要提示一点,在.dts文件里我们经常会看到target = &ocp,这里的ocp是on chip peripherals的缩写,我猜想可能是用来描述连接到芯片的其他外设的(如按键、lcd等)。后面的日志里我再记录一下详细的操作细节。这篇先简单介绍这么多。

三、编译.dts文件
===============================================================
写好.dts文件以后需要用dtc编译器编译一下,生成.dtbo文件才能使用。
假设我们写好了一个名为ADAFRUIT-SPI0-00A0.dts的文件,编译指令如下

# dtc -O dtb -o ADAFRUIT-SPI0-00A0.dtbo -b 0 -@ ADAFRUIT-SPI0-00A0.dts

然后就会生成ADAFRUIT-SPI0-00A0.dtbo文件。下面解释一下各个参数
-O dtb 声明输出格式为dtb文件
-o 输出文件名
-b 设置启动CPU
-@ (我不太清楚这项是干嘛的,似乎是overlay专有的一项)
注意文件的命名,一定是“程序名-版本号.dtbo(.dts)”的形式。
编译完成以后,一定要把.dtbo文件放到/lib/firmware/目录下才能使用

# cp ADAFRUIT-SPI0-00A0.dtbo /lib/firmware

四、overlay的使用 (Exporting and Unexporting an Overlay,加载和卸载)
===============================================================
所有已经加载的overlay列表都在/sys/devices/bone_capemgr.*/slots这个文件中。(bone_capemgr.*中的*号实际是一个数字,但是每次系统启动时这个数字可能会变化,所以我们用通配符*代替。)我们打开这个文件看一看
# cat /sys/devices/bone_capemgr.*/slots

0: 54:PF--- 
1: 55:PF--- 
2: 56:PF--- 
3: 57:PF--- 
4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-L Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI


我们看到系统已经自动加载了两个overlay,eMMC和HDMI。下面我们把之前讲解的BB-UART1-00A0.dtbo加载一下,方法是

# echo BB-UART1 > /sys/devices/bone_capemgr.*/slots

然后我们再打开slots文件看看有什么变化

0: 54:PF--- 
1: 55:PF--- 
2: 56:PF--- 
3: 57:PF--- 
4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-L Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI
6: ff:P-O-L Override Board Name,00A0,Override Manuf,BB-UART1


会发现多了一项,说明加载成功了,下面就可以使用外设了。

外设使用完毕以后,如何卸载呢?一种方法是重启系统,另一种是

# echo -6 > /sys/devices/bone_capemgr.*/slots

但是在最近的Angstrom系统中,用这种方法会导致kernel panic,然后ssh会断开,所以现在还是用重启系统的方法吧。相信今后这个问题应该很快会解决的。

注:adafruit的这篇Introduction to the BeagleBone Black Device Tree令我受益匪浅,在此表示一下感谢。