Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob

Linux Device Tree - FMAC

lock attach
Attachments are accessible only for community members.

Linux Device Tree - FMAC

PrasanthR_06
Employee
Employee
First comment on KBA 5 questions asked First question asked

This blog post explains the basic concepts of Linux Device Tree and takes some examples to explain how the FMAC WiFi/BT Device Tree has built up for NXP's i.MX6 platform.

Introduction

A Device Tree is a data structure, describing the hardware components of a particular computer so that the operating system's kernel can use and manage those components, including CPU, memory, buses and peripherals. The device tree was derived from SPARC-based workstations and servers via the Open Firmware project and has been chosen as the default mechanism to pass low-level hardware information from the bootloader to the kernel.

 

One of the more challenging aspects of porting Linux (and U-Boot) to your new board is the recent requirement for a device tree blob (DTB). It is also referred to as "Flat Device Tree". Prior to the requirement for a DTB, U-Boot would pass a board information structure to the kernel, which was derived from a header file in U-Boot that had to exactly match the contents of a similar header file in the kernel. It was very difficult to keep them in sync, and it didn't scale well. This was, in part, the motivation for incorporating the flat device tree as a method to communicate low-level hardware details from the bootloader to the kernel.

Reference

Sample Data Format

The Linux device tree is a simple tree structure of nodes and properties. The properties are key-value pairs, and node may contain both properties and child nodes.

For example, the following is a simple tree in the .dts format which shows the structure of nodes and properties.

    • single root node: "/"
    • couple of child nodes: "node1" and "node2"
    • couple of children for node1: "child-node1" and "child-node2"
    • bunch of properties scattered through the tree.

 

/dts-v1/;

/ {

    node1 {

        a-string-property = "A string";

        a-string-list-property = "first string", "second string";

        // hex is implied in byte arrays. no '0x' prefix is required

        a-byte-data-property = [01 23 34 56];

        child-node1 {

            first-child-property;

            second-child-property = <1>;

            a-string-property = "Hello, world";

        };

        child-node2 {

        };

    };

    node2 {

        an-empty-property;

        a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */

        child-node1 {

        };

    };

};

GPIO Device Tree

With the basic concepts explained above, let us try to configure a GPIO on iMX6SX platform from device tree. Make sure to keep the following resources available.

    • Linux kernel source code
    • iMX6SX Board schematic
    • iMX6SX Datasheet

From the schematic of iMX6SX, we have two user buttons (KEY_FUNC1 and KEY_FUNC2). Let us select the KEY_FUNC1 (User button 1) in this example. The KEY_FUNC1 is mapped to CSI_DATA04 which internally maps to GPIO1_IO18(from iMX6SX datasheet reference).

 

The basic convention to find the GPIO number: Linux GPIO Number = (gpio_bank - 1) * 32 + gpio_bit. In this case, GPIO number = (1 - 1) * 32 + 18, which gives GPIO number = 18. Therefore, we need to configure the GPIO number 18 in device tree.

 

Linux already have "gpio-keys" driver that translates GPIO events in key/button events. Let us look at how we can generate a key code(say, KEY_NUMLOCK code) when a button is pressed.

gpio-keys {

        compatible = "gpio-keys";

        left-key {

            label = "Left key";

            gpios = <&gpio1 18 0>;

            linux,code = <69>; /* KEY_NUMLOCK */

        };

}

Now, compile the modified DTS file using DTC compiler. The following command can be used to compile DTS files for all flavors.

    • make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- dtbs

Copy the corresponding .DTB file to SD card boot directory and boot the image on platform. Test the key press and check the event generated with the key code that  configured in DTS file.

    • root@imx6sxsabresd:~# cd /dev/input/by-path/

    • root@imx6sxsabresd:/dev/input/by-path# evtest platform-gpio-keys-event

Input driver version is 1.0.1

Input device ID: bus 0x19 vendor 0x1 product 0x1 version 0x100

Input device name: "gpio-keys"

Supported events:

  Event type 0 (EV_SYN)

  Event type 1 (EV_KEY)

    Event code 69 (KEY_NUMLOCK)

    Event code 114 (KEY_VOLUMEDOWN)

Properties:

Testing ... (interrupt to exit)

Event: time 1519994189.663102, type 1 (EV_KEY), code 69 (KEY_NUMLOCK), value 1

Event: time 1519994189.663102, -------------- SYN_REPORT ------------

Event: time 1519994189.793001, type 1 (EV_KEY), code 69 (KEY_NUMLOCK), value 0

    • root@imx6sxsabresd:/dev/input/by-path# cat /proc/interrupts

...

103:          2  gpio-mxc  18 Edge      Left key

...

 

In similar way, we can enhance the DTS to generate all key code as needed for target application. Below is sample code which takes two user buttons on iMX6SX for Volume UP and DOWN configuration.

        gpio-keys {

                compatible = "gpio-keys";

                pinctrl-names = "default";

                pinctrl-0 = <&pinctrl_gpio_keys>;

                volume-up {

                        label = "Volume Up";

                        gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;

                        linux,code = <KEY_VOLUMEUP>;

                };

                volume-down {

                        label = "Volume Down";

                        gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;

                        linux,code = <KEY_VOLUMEDOWN>;

                };

        };

 

For each key, add the corresponding GPIO in sub-node of pinctrl node. The iMX6SX allows Pin/Pad IO MUXing. Refer to "arch/arm/boot/dts/imx6sx-pinfunc.h" for list of IOMUX definition. For instance, the MX6SX_PAD_CSI_DATA04__GPIO1_IO_18 shows the mapping between CSI_DATA04 and GPIO1_IO18.

 

        pinctrl_gpio_keys: gpio_keysgrp {

                fsl,pins = <

                    MX6SX_PAD_CSI_DATA04__GPIO1_IO_18 0x17059

                    MX6SX_PAD_CSI_DATA05__GPIO1_IO_19 0x17059

                >;

        };

Pin multiplexing (IOMUX)

The i.MX System-On-Chip has a lot of functionality but a limited number of pins (or pads). Even though a single pin can only perform one function at a time, they can be configured internally to perform different functions. This is called Pin Multiplexing.

 

Refer PadMux/Control​ which explains better about PAD/PIN IOMUX settings.

 

The macro MX6SX_PAD_CSI_DATA04__GPIO1_IO_18 has 5 integers. These five integers are:

IOMUX register offset (0x005C)

Pad configuration register offset (0x03A4)

Select input daisy chain register offset (0x0000)

IOMUX configuration setting (0x5)

Select input daisy chain setting (0x0)

 

The sixth integer is mentioned in DTS file 0x17059  which describes the pin's electrical behavior. More details about electrical definition can be obtained in kernel source tree: "Documentation/devicetree/bindings/pinctrl/fsl,imx6sx-pinctrl.txt"

FMAC BT-WiFi Device Tree

As mentioned above, the i.MX SOC has IOMUX module built in. The IOMUX module controls a pin usage so that the same pin can be configured for different purposes and can be used by different modules. This is a common way to reduce the pin count while meeting the requirements from various customers. Platforms that do not have the IOMUX hardware module can do pin muxing through the GPIO module.

 

For reference, please take look at the DTS file attached​ (imx6sx-sdb-btwifi-fmac.dts) which has device tree source support for plugging in Murata Wi-Fi/BT EVK into SD3 slot using Murata i.MX InterConnect v1.0 adapter and SD Card Extender on SD2 slot. Bluetooth UART connects via SD3 EMMC/MMC Plus pinout and WL_REG_ON/BT_REG_ON/WL_HOST_WAKE connect via SD Card Extender.

 

Since the dongle is a WiFi-BT combo type, the DTS need to declare the pins needed for BT_REG_ON, WL_REG_ON, and WL_HOST_WAKE. We can see that it takes IOMUX pins for data/control signals to communicate between dongle and host.

The IOMUX section contains four groups: btgrp, uart3grp, usdhc2grp-1 and wifigrp.

 

btgrp

  • SD2_DATA3 pin mux'd to GPIO6_IO11 and used for BT_REG_ON

pinctrl_bt: btgrp {

     fsl,pins = <

          MX6SX_PAD_SD2_DATA3__GPIO6_IO_11 0x13069 /* BT_REG_ON */

     >;

};

uart3grp

  • SD3 data signals(4-7) mux'd to UART3 RX, TX, RTS, CTS respectively.
  • BT communication via UART

pinctrl_uart3: uart3grp {

     fsl,pins = <

          MX6SX_PAD_SD3_DATA4__UART3_RX 0x1b0b1

          MX6SX_PAD_SD3_DATA5__UART3_TX 0x1b0b1

          MX6SX_PAD_SD3_DATA7__UART3_CTS_B 0x1b0b1

          MX6SX_PAD_SD3_DATA6__UART3_RTS_B 0x1b0b1

     >;

};

usdhc2grp-1

  • Change MUXing on SD2 slot for control signals.

pinctrl_usdhc2_1: usdhc2grp-1 {

     fsl,pins = <

          MX6SX_PAD_SD2_CMD__USDHC2_CMD 0x17059

          MX6SX_PAD_SD2_CLK__USDHC2_CLK 0x10059

          MX6SX_PAD_SD2_DATA0__USDHC2_DATA0 0x17059

     >;

};

wifigrp

  • Murata change SD3 to 4-bit SDIO only; use upper 4-bits for UART.
  • Note the WL_HOST_WAKE and WL_REG_ON declared below.

pinctrl_wifi: wifigrp {

     fsl,pins = <

          MX6SX_PAD_SD3_CMD__USDHC3_CMD 0x17069

          MX6SX_PAD_SD3_CLK__USDHC3_CLK 0x10071

          MX6SX_PAD_SD3_DATA0__USDHC3_DATA0 0x17069

          MX6SX_PAD_SD3_DATA1__USDHC3_DATA1 0x17069

          MX6SX_PAD_SD3_DATA2__USDHC3_DATA2 0x17069

          MX6SX_PAD_SD3_DATA3__USDHC3_DATA3 0x17069

          MX6SX_PAD_KEY_COL0__GPIO2_IO_10 0x17059 /* CD */

          MX6SX_PAD_KEY_ROW0__GPIO2_IO_15 0x17059 /* WP */

          /* Murata Module control signals */

          MX6SX_PAD_SD2_DATA1__GPIO6_IO_9         0x13069 /* WL_HOST_WAKE */

          MX6SX_PAD_SD2_DATA2__GPIO6_IO_10 0x13069 /* WL_REG_ON */

     >;

};

Now, apply the above IOMUX pin control settings to actual group in DTS file (for instance, map the uart3grp to its corresponding parent module uart3).

&uart3 {

     pinctrl-names = "default";

     pinctrl-0 = <&pinctrl_uart3

                         &pinctrl_bt>;

     fsl,uart-has-rtscts;

     status = "okay";

};

&usdhc2 {

     pinctrl-names = "default";

     pinctrl-0 = <&pinctrl_usdhc2_1>;

     bus-width = <1>;

};

&usdhc3 {

     #address-cells = <1>;

     #size-cells = <0>;

     pinctrl-names = "default";

     pinctrl-0 = <&pinctrl_wifi>;

     bus-width = <4>;

     no-1-8-v; /* force 3.3V VIO */

     non-removable;

     mmc-pwrseq = <&usdhc3_pwrseq>;

     pm-ignore-notify;

     status = "okay";

     brcmf: bcrmf@1 {

          reg = <1>;

          compatible = "brcm,bcm4329-fmac";

     };

};

As highlighted above, it declares "brcm" in "compatible" property. The Kernel is responsible to load the correct drivers requested by DTS and apply the configuration, by parsing all the properties in DTS.

Device Tree Compiler

The device tree compiler (dtc) converts the human-readable device tree source into the machine-readable binary that both U-Boot and the Linux kernel understand. Although a git tree is hosted on kernel.org for dtc, the device tree source has been merged into the kernel source tree.

It is quite straightforward to use the device tree compiler. A typical command to convert source to binary looks like this:

    • dtc -O dtb -o <myboard>.dtb -b 0 <myboard>.dts

In this command, myboard.dts is the device tree human-readable source, and myboard.dtb is the binary created by this command invocation. The -O flag specifies the output format—in this case, the device tree blob binary. The -o flag names the output file, and the -b 0 parameter specifies the physical boot CPU in the multicore case.Note that the dtc compiler allows you to go in both directions. The command example just shown performs a compile from source to device tree binary, whereas a command shown below, produces source from the binary:

    • dtc -I dtb -O dts imx6sx-sdb.dtb >imx6sx-sdb.dts

(Note: Device tree sources in the kernel deviate from the regular syntax, by using the cpp preprocessor for includes and substitution. Please check here​ for more information.)

The simplest way is to use "make" commands and build the DTB for many well-known reference boards directly from the kernel source. The command to generate DTB for i.MX6SX platform is shown below:

    • make ARCH=arm imx6sx-sdb.dtb

To compile all DTS and generate its DTB, use the following command.

    • make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- dtbs

 

Load DTB file

  • Copy the DTB file generated in the boot partition of SD card.
  • Power on the device and stop at boot prompt.
  • Set DTB to correct file name using below command.
    • setenv fdt_file imx6sx-sdb-btwifi-fmac
    • saveenv
    • reset

 

Attachments
0 Likes
6812 Views