ZYNQ #EC1 PL端模拟HDMI输出,i2c接入PCA9548复用器后设备树的分析

本文为番外篇,分析了在前面 ZYNQ7000 #1 - PL端模拟HDMI信号输出环境下的Linux界面显示 基础上,当HDMI的I2c总线非直接接入而是通过一个 i2c 选择器后,如何进行设备树修改,进行哪些相关资料参考。

 

目录

1 - 楔子

2 - 参考与借鉴

2.1 - 参考1 : armada-385-turris-omnia.dts

2.2 - 参考2:imx6q-b850v3.dts

2.3 - 参考3:Digilent的 digillent_encoder文档

2.4 - 参考4:设备树binding文档中的i2c-mux.txt与i2c-mux-pca954x.txt

3 - 硬件搭建

4 - 编写

5 - 注释与解析


1 - 楔子

米联客MZ7035FD的HDMI接口上,其用于与设备通讯获取设备屏幕参数EDID的i2c接口,并非直接从PL的引脚出来布线到HDMI-A接口上,而是经过了一个i2c复用器PCA9548APW

这里我们希望能够使用我们之前部署的 Digilent提供的视频解码驱动(encoder)和时钟驱动(clk),来实现自动识别输出分辨率,而不用每次更改PL端的代码并重新编译。

参考前面的文章 ZYNQ7000 #1 - PL端模拟HDMI信号输出环境下的Linux界面显示 ,我们知道,只需要在设备树中将链接到HDMI-A接口的 i2c 主设备作为参数,输入给 amba_pl 总线下的 hdmi_encoder 节点、xilinx_drm 节点的 edid-i2c 即可。

参考下面的代码,注意加粗部分。有部分删减,详情请参考上面链接文章中的代码:

&i2c0 {
    clock-frequency = <100000>;
};
 
&amba_pl {
 
    hdmi_encoder_0:hdmi_encoder {
        compatible = "digilent,drm-encoder";
        digilent,edid-i2c = <&i2c0>;
    };
 
    xilinx_drm {
        compatible = "xlnx,drm";
        xlnx,vtc = <&v_tc_0>;
        xlnx,connector-type = "HDMIA";
        xlnx,encoder-slave = <&hdmi_encoder_0>;
        clocks = <&axi_dynclk_0>;
        dglnt,edid-i2c = <&i2c0>;
        planes {
            xlnx,pixel-format = "rgb888";
            plane0 {
                dmas = <&axi_vdma_0 0>;
                dma-names = "dma";
            };
        };
    };
};
 

这段设备树代码是在黑金板子上使用的,在 i2c0 直连的时候,一切都显得那么简单,但是在米联客这块板子上,我们应该如何修改设备树代码,使得支持 PCA9548 这个 i2c 复用器呢?

2 - 参考与借鉴

2.1 - 参考1 : armada-385-turris-omnia.dts

我们可以参考别人的设备树,这里我参考的设备树(armada-385-turris-omnia.dts)如下,有部分删减:

&i2c0 {
	pinctrl-names = "default";
	pinctrl-0 = <&i2c0_pins>;
	status = "okay";

	i2cmux@70 {
		compatible = "nxp,pca9547";
		#address-cells = <1>;
		#size-cells = <0>;
		reg = <0x70>;
		status = "okay";
...

		i2c@4 {
			#address-cells = <1>;
			#size-cells = <0>;
			reg = <4>;

			/* routed to SFP+ */
		};
...
	};
};

2.2 - 参考2:imx6q-b850v3.dts

查看 imx6q-b850v3.dts 设备树,有删减。

&i2c2 {
	pca9547_ddc: mux@70 {
		compatible = "nxp,pca9547";
		reg = <0x70>;
		#address-cells = <1>;
		#size-cells = <0>;

		mux2_i2c1: i2c@0 {
			#address-cells = <1>;
			#size-cells = <0>;
			reg = <0x0>;
		};

...
	};
};

&hdmi {
	ddc-i2c-bus = <&mux2_i2c1>;
};

在 imx6q-b850v3.dts 的顶层包含 imx6q.dtsi 有如下代码

&hdmi {
	compatible = "fsl,imx6q-hdmi";

	port@2 {
		reg = <2>;

		hdmi_mux_2: endpoint {
			remote-endpoint = <&ipu2_di0_hdmi>;
		};
	};

	port@3 {
		reg = <3>;

		hdmi_mux_3: endpoint {
			remote-endpoint = <&ipu2_di1_hdmi>;
		};
	};
};

查看 Document 下的设备树的binding文档,找到 imx6q-hdmi 的相关文档(Documentation/devicetree/bindings/display/imx/hdmi.txt)

Freescale i.MX6 DWC HDMI TX Encoder
===================================

The HDMI transmitter is a Synopsys DesignWare HDMI 1.4 TX controller IP
with a companion PHY IP.

These DT bindings follow the Synopsys DWC HDMI TX bindings defined in
Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt with the
following device-specific properties.


Required properties:

- compatible : Shall be one of "fsl,imx6q-hdmi" or "fsl,imx6dl-hdmi".
- reg: See dw_hdmi.txt.
- interrupts: HDMI interrupt number
- clocks: See dw_hdmi.txt.
- clock-names: Shall contain "iahb" and "isfr" as defined in dw_hdmi.txt.
- ports: See dw_hdmi.txt. The DWC HDMI shall have between one and four ports,
  numbered 0 to 3, corresponding to the four inputs of the HDMI multiplexer.
  Each port shall have a single endpoint.
- gpr : Shall contain a phandle to the iomuxc-gpr region containing the HDMI
  multiplexer control register.

Optional properties

- ddc-i2c-bus: The HDMI DDC bus can be connected to either a system I2C master
  or the functionally-reduced I2C master contained in the DWC HDMI. When
  connected to a system I2C master this property contains a phandle to that
  I2C master controller.

可以看到,ddc-i2c-bus 要赋值的应该是一个 i2c 主设备。

这份参考指明了可以直接从 i2c-mux 下的分枝直接引用为 i2c 设备

2.3 - 参考3:Digilent的 digillent_encoder文档

可以在这里(https://github.com/Digilent/linux-digilent/blob/master/Documentation/devicetree/bindings/drm/xilinx/digilent_encoder.txt)查看我们需要用的digilent_encoder的相关文档,文档中这样写到

Device-Tree bindings for Digilent DRM Encoder Slave

This driver provides support for VGA and HDMI outputs on Digilent FPGA boards. The
VGA or HDMI port must be connected to a Xilinx display pipeline via an axi2vid
IP core.

Required properties:
 - compatible: Should be "digilent,drm-encoder".

Optional properties:
 - digilent,edid-i2c: The I2C device connected to the DDC bus on the video connector.
                      This is used to obtain the supported resolutions of an attached 
                      monitor. If not defined, then a default set of resolutions is used
                      and the display will initialize to the closest available size 
                      specified by vpref and hpref. Note most VGA connectors on Digilent 
                      boards do not have the DDC bus routed out. 

2.4 - 参考4:设备树binding文档中的i2c-mux.txt与i2c-mux-pca954x.txt

在 Documentation/devicetree/bindings/i2c下我们可以看到这两篇相关文档

i2c-mux.txt

Common i2c bus multiplexer/switch properties.

An i2c bus multiplexer/switch will have several child busses that are
numbered uniquely in a device dependent manner.  The nodes for an i2c bus
multiplexer/switch will have one child node for each child bus.

Optional properties:
- #address-cells = <1>;
   This property is required is the i2c-mux child node does not exist.

- #size-cells = <0>;
   This property is required is the i2c-mux child node does not exist.

- i2c-mux
   For i2c multiplexers/switches that have child nodes that are a mixture
   of both i2c child busses and other child nodes, the 'i2c-mux' subnode
   can be used for populating the i2c child busses.  If an 'i2c-mux'
   subnode is present, only subnodes of this will be considered as i2c
   child busses.

Required properties for the i2c-mux child node:
- #address-cells = <1>;
- #size-cells = <0>;

Required properties for i2c child bus nodes:
- #address-cells = <1>;
- #size-cells = <0>;
- reg : The sub-bus number.

Optional properties for i2c child bus nodes:
- Other properties specific to the multiplexer/switch hardware.
- Child nodes conforming to i2c bus binding


Example :

    /*
       An NXP pca9548 8 channel I2C multiplexer at address 0x70
       with two NXP pca8574 GPIO expanders attached, one each to
       ports 3 and 4.
     */

    mux@70 {
        compatible = "nxp,pca9548";
        reg = <0x70>;
        #address-cells = <1>;
        #size-cells = <0>;

        i2c@3 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <3>;

            gpio1: gpio@38 {
                compatible = "nxp,pca8574";
                reg = <0x38>;
                #gpio-cells = <2>;
                gpio-controller;
            };
        };
        i2c@4 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <4>;

            gpio2: gpio@38 {
                compatible = "nxp,pca8574";
                reg = <0x38>;
                #gpio-cells = <2>;
                gpio-controller;
            };
        };
    };

i2c-mux-pca954x.txt

* NXP PCA954x I2C bus switch

Required Properties:

  - compatible: Must contain one of the following.
    "nxp,pca9540", "nxp,pca9542", "nxp,pca9543", "nxp,pca9544",
    "nxp,pca9545", "nxp,pca9546", "nxp,pca9547", "nxp,pca9548"

  - reg: The I2C address of the device.

  The following required properties are defined externally:

  - Standard I2C mux properties. See i2c-mux.txt in this directory.
  - I2C child bus nodes. See i2c-mux.txt in this directory.

Optional Properties:

  - reset-gpios: Reference to the GPIO connected to the reset input.
  - i2c-mux-idle-disconnect: Boolean; if defined, forces mux to disconnect all
    children in idle state. This is necessary for example, if there are several
    multiplexers on the bus and the devices behind them use same I2C addresses.
  - interrupt-parent: Phandle for the interrupt controller that services
    interrupts for this device.
  - interrupts: Interrupt mapping for IRQ.
  - interrupt-controller: Marks the device node as an interrupt controller.
  - #interrupt-cells : Should be two.
    - first cell is the pin number
    - second cell is used to specify flags.
    See also Documentation/devicetree/bindings/interrupt-controller/interrupts.txt

Example:

    i2c-switch@74 {
        compatible = "nxp,pca9548";
        #address-cells = <1>;
        #size-cells = <0>;
        reg = <0x74>;

        interrupt-parent = <&ipic>;
        interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
        interrupt-controller;
        #interrupt-cells = <2>;

        i2c@2 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <2>;

            eeprom@54 {
                compatible = "at,24c08";
                reg = <0x54>;
            };
        };

        i2c@4 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <4>;

            rtc@51 {
                compatible = "nxp,pcf8563";
                reg = <0x51>;
            };
        };
    };

综上,我们可以知道:

  • 要向 digilent_encoder 端点提供的参数应当是一个 i2c 主设备(总线控制),其通过HDMI线路连接到对接的HDMI显示设备芯片上,内核通过该总线获取对接设备的EDID。
  • 在 i2c-mux 下子节点可以直接作为 i2c 节点设备给其他设备引用;其下的子总线可以直接作为 i2c 总线给其他设备用

3 - 硬件搭建

这里我使用的是米联客MZ7035FD开发板,参考之前的ZYNQ7000 #1 - PL端模拟HDMI信号输出环境下的Linux界面显示,我们将digilent的两个需要用到的IP放入我们的FPGA框图设计中

添加引脚约束文件,这里主要是为了将TMDS信号引出到板子的HDMI接口上。注意引脚的电平问题,不同的硬件环境需要进行相关的修改,不同的引脚也只支持不同的电平

set_property IOSTANDARD LVDS [get_ports TMDS_clk_p]
set_property IOSTANDARD LVDS [get_ports {TMDS_data_p[0]}]
set_property IOSTANDARD LVDS [get_ports {TMDS_data_p[1]}]
set_property IOSTANDARD LVDS [get_ports {TMDS_data_p[2]}]
set_property IOSTANDARD LVCMOS18 [get_ports hdmi_ddc_scl_io]
set_property IOSTANDARD LVCMOS18 [get_ports hdmi_ddc_sda_io]

#package pin
set_property PACKAGE_PIN J14 [get_ports TMDS_clk_p]
set_property PACKAGE_PIN K13 [get_ports {TMDS_data_p[0]}]
set_property PACKAGE_PIN G14 [get_ports {TMDS_data_p[1]}]
set_property PACKAGE_PIN K15 [get_ports {TMDS_data_p[2]}]
set_property PACKAGE_PIN A15 [get_ports hdmi_ddc_scl_io]
set_property PACKAGE_PIN A14 [get_ports hdmi_ddc_sda_io]

#bit compress
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]

4 - 编写

假设我们的这个 PCA9548 是挂载在 pl 引出的 i2c0上,PCA9548 的地址如上面的原理图所示,三个地址位都被拉到了地,因此它在 i2c0 设备上的地址应当是 01110000 = 0x70 (七位数据位)。我们需要用到的 HDMI 的 i2c 接到了 PCA9548 的第四个分位上。

一葫芦画瓢,我们可以写出下面的设备树代码,作为 system-user.dtsi 。着重看 &i2c0 、hdmi_encoder 与 xilinx_drm 节点:

/include/ "system-conf.dtsi"
 
/ {
};
 
&i2c0 {
	clock-frequency = <100000>;
	status = "okay";

	i2cmux@70 {
		compatible = "nxp,pca9548";
		#address-cells = <1>;
		#size-cells = <0>;
		reg = <0x70>;
		status = "okay";
		
		i2cH1:i2chdmi@4{
			#address-cells = <1>;
			#size-cells = <0>;
			reg = <4>;

			/* routed to HDMI connector (H1) */
		};
	};
};

&amba_pl {
	hdmi_encoder_0:hdmi_encoder {
		compatible = "digilent,drm-encoder";
		digilent,edid-i2c = <&i2cH1>;
	};
 
	xilinx_drm {
		compatible = "xlnx,drm";
		xlnx,vtc = <&v_tc_0>;
		xlnx,connector-type = "HDMIA";
		xlnx,encoder-slave = <&hdmi_encoder_0>;
		clocks = <&axi_dynclk_0>;
		dglnt,edid-i2c = <&i2cH1>;
		planes {
			xlnx,pixel-format = "rgb888";
			plane0 {
				dmas = <&axi_vdma_0 0>;
				dma-names = "dma";
			};
		};
	};
};

&axi_dynclk_0 {
	compatible = "digilent,axi-dynclk";
	#clock-cells = <0>;
	clocks = <&clkc 15>;
};

&v_tc_0 {
	compatible = "xlnx,v-tc-5.01.a";
};

5 - 注释与解析

这里加一段部分分析与注释

&i2c0 {                                                                          /* PCA9548挂载在了i2c0上 */
    clock-frequency = <100000>;
    status = "okay"

    i2cmux@70 {                                                           /* PCA9548在i2c0上的地址是0x70 */
        compatible = "nxp,pca9548";                            /* 驱动适配 */
        #address-cells = <1>;
        #size-cells = <0>;
        reg = <0x70>;                                                       /* 地址0x70 */
        status = "okay";
        
        i2cH1:i2chdmi@4{                                                /* 我们需要用的只有4号分位 */
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <4>;                                                         /* 4 */

            /* routed to HDMI connector (H1) */
        }
    }
};

&amba_pl {
......

    hdmi_encoder_0:hdmi_encoder {
        compatible = "digilent,drm-encoder";
        digilent,edid-i2c = <&i2cH1>;                                 /* 注意引用 */
    };
 
    xilinx_drm {
        compatible = "xlnx,drm";
        xlnx,vtc = <&v_tc_0>;
        xlnx,connector-type = "HDMIA";
        xlnx,encoder-slave = <&hdmi_encoder_0>;
        clocks = <&axi_dynclk_0>;
        dglnt,edid-i2c = <&i2cH1>;                                     /* 注意引用 */
        planes {
            xlnx,pixel-format = "rgb888";
            plane0 {
                dmas = <&axi_vdma_0 0>;
                dma-names = "dma";
            };
        };
    };
};

以上就是设备树的修改,记得在内核编译时将 PCA954x 编译入内核

6 - 接上hdmi屏

这里有几个注意的地方

  1. 参考我的这篇文章 ZYNQ7000 #1 - PL端模拟HDMI信号输出环境下的Linux界面显示 里,修改99-fbdev.conf ,为xorg指定framebuffer
  2. 可以参考 基于ubuntu-base进行根文件系统的修改与打包 ,让xorg开机启动的脚本。如果手动操作,记得先载入DISPLAY变量,再唤起xsession(即先 export DISPLAY=:0.0,再 startx)。记得开启 xhost +来使得允许连接到此x服务
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章