Manually editing Device Tree (.dts) files

Discussion in 'UDOO QUAD' started by fetcher, Nov 20, 2018.

  1. fetcher

    fetcher Member

    Mar 9, 2014
    Likes Received:
    Udoobuntu's 'dtweb' graphical utility is handy for editing the device-tree and changing pin-mux modes, but in some cases it's necessary to edit the .dts source file manually (for instance, if you want to map UART5 to the double-row header rather than the front row, or use SPDIF-OUT and I2C bus 1 simultaneously).

    If you've already installed a custom Device Tree using the graphical tool, this should exist under /boot/dts-overlay rather than the default /boot/dts directory. The file /boot/uEnv.txt tells U-boot which directory to look in-- a line reading "use_custom_dtb=true" in that file will divert it to dts-overlay (if you edit uEnv.txt, be careful not to add carriage-return ^M / 0x0d characters ahead of line breaks, as Windows editors like to do ... best to just edit it under Linux).

    Afterwards, it's necessary to recompile a .dtb (Device Tree Blob) using the 'dtc' tool. e.g.
    cd /boot/dts-overlay
    dtc -I dts -O dtb imx6q-udoo-hdmi.dts >imx6q-udoo-hdmi.dtb
    If dtc is not already available, run "apt-get install device-tree-compiler" to pull it in. This tool will catch syntax errors or similar obvious mistakes in the .dts (typical of Unix tools, silent output indicates successful compilation), but certain problems, such as pin conflicts will not display an error until you try to reboot with the new .dtb.

    Needless to say, it's a good idea to make backups of both files before making any changes!

    In most cases you can turn on a disabled device, such as an extra UART by simply finding its control block, then changing 'status = "disabled"' to 'status = "okay"' underneath. Be sure to spell out the word "okay" rather than "enabled", and keep the quotes intact.

    Look to the "aliases" section up top for hints. This line in aliases, for instance,
    serial4 = "/soc/aips-bus@02100000/serial@021f4000";
    points you to serial@021f4000 to enable UART5. (It's labeled serial4 here rather than 5 because the numbering starts with 0, as with ttymxc device numbers).

    It's also necessary to change the i.MX6 pin-muxing to route signals from that newly-enabled device out to physical pins, though, and this step is a bit more involved, because you have to find the hex addresses corresponding to the desired pins' IOMUX registers, remove these from 'hoggrp-2' (hoggrp entries denote simple GPIO mappings), then add them to an block corresponding to the newly-enabled device (see below for more detail; this is different from the actual device-control block where you changed "disabled" to "okay" earlier - mux'ing is all under master heading iomuxc@020e0000).

    The Udoo dtweb tool unfortunately creates a hoggrp-2 block where all the pin-mux registers are run together into one humongous line, which makes manual edits very tedious. However, the .dts file compiler doesn't care about whitespace or line breaks, and allows for C-style comments, so you can break this section up and add labels.

    Here's my cleaned-up hoggrp-2 block as an example, with labels taken from arch/arm/boot/dts/imx6qdl-udoo-externalpins.dtsi in the kernel source tree (you can't use those labels in boot-device .dts context, but it doesn't hurt to include them as comments).

    Note that I've used leading // comment marks to disable GPIO mappings for specific pins that correspond to other devices in use, specifically the second SD card lot (SD1) on front-row pins, UART3, UART5, and SPDIF-OUT (but not SPDIF-IN) on the side double-row header. These can be easily turned back into GPIOs by simply removing the // in front:
                                            hoggrp-2 {
     fsl,pins = <0x284 0x654 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_CSI0_DAT11__GPIO5_IO29 PIN-0
                 0x280 0x650 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_CSI0_DAT10__GPIO5_IO28 -1
    //           0x350 0x738 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD1_CLK__GPIO1_IO20   -2 sd1-clk
    //           0x340 0x728 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD1_DAT0__GPIO1_IO16  -3 sd1-dat0
    //           0x33c 0x724 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD1_DAT1__GPIO1_IO17  -4 sd1-dat1
    //           0x348 0x730 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD1_CMD__GPIO1_IO18   -5 sd1-cmd
                 0x320 0x708 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD4_DAT1__GPIO2_IO09  -6
                 0x324 0x70c 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD4_DAT2__GPIO2_IO10  -7
    //           0x344 0x72c 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD1_DAT3__GPIO1_IO21  -8 sd1-dat3
    //           0x34c 0x734 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD1_DAT2__GPIO1_IO19  -9 sd1-dat2
                 0x224 0x5f4 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_GPIO_1__GPIO1_IO01    -10
                 0x228 0x5f8 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_GPIO_9__GPIO1_IO09    -11
                 0x22c 0x5fc 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_GPIO_3__GPIO1_IO03    -12
                 0x31c 0x704 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_SD4_DAT0__GPIO2_IO08  -13
                 0x268 0x638 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_CSI0_DAT4__GPIO5_IO22 -14
                 0x298 0x668 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_CSI0_DAT16__GPIO6_IO02 -15
                 0x290 0x660 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_CSI0_DAT14__GPIO6_IO00 -16
                 0x294 0x664 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_CSI0_DAT15__GPIO6_IO01 -17
                 0x288 0x658 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_CSI0_DAT12__GPIO5_IO30 -18
                 0x28c 0x65c 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_CSI0_DAT13__GPIO5_IO31 -19
    //           0xc4 0x3d8 0x0 0x5 0x0 0x80000000  //MX6QDL_PAD_EIM_D28__GPIO3_IO28   -20 i2c0-sda
    //           0xa4 0x3b8 0x0 0x5 0x0 0x80000000  //MX6QDL_PAD_EIM_D21__GPIO3_IO21   -21 i2c0-scl
                 0x188 0x49c 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT6__GPIO4_IO27 -22
                 0x18c 0x4a0 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT7__GPIO4_IO28 -23
                 0x190 0x4a4 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT8__GPIO4_IO29 -24
                 0x194 0x4a8 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT9__GPIO4_IO30 -25
                 0x198 0x4ac 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT10__GPIO4_IO31 -26
                 0x19c 0x4b0 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT10__GPIO4_IO05 -27
                 0x1a0 0x4b4 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT12__GPIO5_IO06 -28
                 0x1a4 0x4b8 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT13__GPIO5_IO07 -29
                 0x1a8 0x4bc 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT14__GPIO5_IO08 -30
                 0x1ac 0x4c0 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 -31
                 0x1b0 0x4c4 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT16__GPIO5_IO10 -32
                 0x1b4 0x4c8 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT17__GPIO5_IO11 -33
                 0x1b8 0x4cc 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT18__GPIO5_IO12 -34
                 0x1bc 0x4d0 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT19__GPIO5_IO13 -35
                 0x1c0 0x4d4 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT20__GPIO5_IO14 -36
                 0x1c4 0x4d8 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT21__GPIO5_IO15 -37
                 0xf4 0x408 0x0 0x5 0x0 0x80000000  //MX6QDL_PAD_EIM_A16__GPIO2_IO22     -38
                 0x250 0x620 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_GPIO_18__GPIO7_IO13     -39
                 0x2fc 0x6e4 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_NANDF_D0__GPIO2_IO00    -40
                 0x308 0x6f0 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_NANDF_D3__GPIO2_IO03    -41
                 0x304 0x6ec 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_NANDF_D2__GPIO2_IO02    -42
                 0x300 0x6e8 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_NANDF_D1__GPIO2_IO01    -43
    //           0x254 0x624 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_GPIO_19__GPIO4_IO05  -44 spdif-out
                 0x1c8 0x4dc 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT22__GPIO5_IO16 -45
                 0x1cc 0x4e0 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_DISP0_DAT22__GPIO5_IO17 -46
    //           0xb8 0x3cc 0x0 0x5 0x0 0x80000000  //MX6QDL_PAD_EIM_D25__GPIO3_IO25   -47 uart3-rx
    //           0x204 0x5d4 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_KEY_ROW1__GPIO4_IO09  -48 uart5-rx
    //           0x200 0x5d0 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_KEY_COL1__GPIO4_IO08  -49 uart5-tx
                 0x100 0x414 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_EIM_OE__GPIO2_IO25   -50
                 0xfc 0x410 0x0 0x5 0x0 0x80000000  //MX6QDL_PAD_EIM_CS1__GPIO2_IO24  -51
                 0xf8 0x40c 0x0 0x5 0x0 0x80000000  //MX6QDL_PAD_EIM_CS0__GPIO2_IO23  -52
    //           0xb4 0x3c8 0x0 0x5 0x0 0x80000000  //MX6QDL_PAD_EIM_D24__GPIO3_IO24  -53 uart3-tx
                 0x244 0x614 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_GPIO_8__GPIO1_IO08  canrx /Ard D68
                 0x240 0x610 0x0 0x5 0x0 0x80000000 //MX6QDL_PAD_GPIO_7__GPIO1_IO07  cantx /Ard D69
                                                    linux,phandle = <0x1d>;
                                                    phandle = <0x1d>;
    (There is another of these sections, labeled just "hoggrp" without the -2, which normally shouldn't need to be changed; this defines some pins not brought out to Arduino header rows; e.g. some on the CN11 camera header are here.)

    [the forum tells me this post is too long, I'll continue in a follow-up]
    waltervl likes this.
  2. fetcher

    fetcher Member

    Mar 9, 2014
    Likes Received:
    [continued from above]

    Now comes perhaps the trickiest part, which is defining the correct IOMUXC register settings to map pins removed from GPIO use (hoggrp-2) to their intended function. For this you'll want to consult the Freescale i.MX6 reference manual, IMX6DQRM.pdf (pages 1951 to 2690), and need to know what each of the six numbers in a row mean. In order, they are:

    1. mux register offset (IOMUXC_SW_MUX_CTL_PAD_*)
    2. pad-control register offset (IOMUXC_SW_PAD_CTL_PAD_*)
    3. input-select register offset (IOMUXC_*_SELECT_INPUT) - not always used; 0 if not
    4. value to place into mux register
    5. value to place into input-select register
    6. value to place into pad-control register - default 0x80000000 for GPIO's

    Yes, these are in a strange order. Here is an example, again using UART5. I comented out its default front-pin-row mappings, and added the double-row pins afterward:
                                            uart5grp {
    //                                              fsl,pins = <0x290 0x660 0x0 0x3 0x0 0x1b0b1  // TX on front row
    //                                                          0x294 0x664 0x940 0x3 0x3 0x1b0b1>;  // RX on front row
                                                    fsl,pins = <0x204 0x5d4 0x0 0x4 0x0 0x1b0b1     // MX6QDL_PAD_KEY_ROW1__UART5_TX_DATA
                                                                0x200 0x5d0 0x940 0x4 0x1 0x1b0b1>; // MX6QDL_PAD_KEY_COL1__UART5_RX_DATA; sel_input -> 1
                                                    linux,phandle = <0x32>;
                                                    phandle = <0x32>;
    Note that compared to the commented-out hoggrp-2 lines above that I labeled uart5-tx and uart5-rx, the first two values on each line (0x204 0x5d4 and 0x200 0x5d0) are the same, but I had to add 0x940 to the RX pin definition in order to set the IOMUXC_UART5_UART_RX_DATA_SELECT_INPUT register. The 940 comes from page 2687 of Freescale's IMX6DQRM document (just search within for UART5), as does the '1' value to poke into this register when it's mapped to KEY_ROW1_ALT4.

    The 0x04 mux-register setting comes from the description of register IOMUXC_SW_MUX_CTL_PAD_KEY_ROW1 on page 2061, but if you've looked up the input register first, the "ALT4" designation mentioned there could be enough of a clue.

    Finally, for the pad-control register, which controls things like internal pull-up/pull-down resistors and slew-rate limitations, I just copied the 0x1b0b1 value from other UART pins. For reference, though, here is what those various pad-control bits actually do:
    // (PADCTL flags, in lieu of 0x80000000) --
    // PAD_CTL_HYS                     (1 << 16)
    // PAD_CTL_PUS_100K_DOWN           (0 << 14)
    // PAD_CTL_PUS_47K_UP              (1 << 14)
    // PAD_CTL_PUS_100K_UP             (2 << 14)
    // PAD_CTL_PUS_22K_UP              (3 << 14)
    // PAD_CTL_PUE                     (1 << 13)
    // PAD_CTL_PKE                     (1 << 12)
    // PAD_CTL_ODE                     (1 << 11)
    // PAD_CTL_SPEED_LOW               (1 << 6)
    // PAD_CTL_SPEED_MED               (2 << 6)
    // PAD_CTL_SPEED_HIGH              (3 << 6)
    // PAD_CTL_DSE_DISABLE             (0 << 3)
    // PAD_CTL_DSE_240ohm              (1 << 3)
    // PAD_CTL_DSE_120ohm              (2 << 3)
    // PAD_CTL_DSE_80ohm               (3 << 3)
    // PAD_CTL_DSE_60ohm               (4 << 3)
    // PAD_CTL_DSE_48ohm               (5 << 3)
    // PAD_CTL_DSE_40ohm               (6 << 3)
    // PAD_CTL_DSE_34ohm               (7 << 3)
    // PAD_CTL_SRE_FAST                (1 << 0)
    // PAD_CTL_SRE_SLOW                (0 << 0)  LSB
    Here is where I enable SPDIF-OUT but not SPDIF-IN (since the latter conflicts with the only i.MX6 I2C bus available on Arduino headers), in case anyone wants to do that:
                                            spdifgrp {
                                                    fsl,pins = <0x254 0x624 0x0 0x2 0x0 0x1b0b0
                                                    // DISABLE SPDIF-IN : 0xa4 0x3b8 0x914 0x7 0x0 0x1b0b0
                                                    linux,phandle = <0x5>;
                                                    phandle = <0x5>;
    And the SPDIF device itself, with corrected clock settings needed to make it work:
                                    spdif@02004000 {
                                            compatible = "fsl,imx35-spdif";
                                            reg = <0x2004000 0x4000>;
                                            interrupts = <0x0 0x34 0x4>;
                                            dmas = <0x4 0xe 0x12 0x0 0x4 0xf 0x12 0x0>;
                                            dma-names = "rx", "tx";
                                            clocks = <0x2 0xf9        // IMX6QDL_CLK_SPDIF_GCLK
                                                      0x2 0x3         // IMX6QDL_CLK_OSC
                                                      0x2 0xc5        // IMX6QDL_CLK_SPDIF
                                                      0x2 0x0         // IMX6QDL_CLK_ASRC 0x6b -> DUMMY
                                                      0x2 0x0         // IMX6QDL_CLK_DUMMY
                                                      0x2 0x0         // IMX6QDL_CLK_ESAI_EXTAL 0x76 -> DUMMY
                                                      0x2 0x3e        // IMX6QDL_CLK_IPG
                                                      0x2 0x0         // IMX6QDL_CLK_MLB 0x8b -> DUMMY
                                                      0x2 0x0         // IMX6QDL_CLK_DUMMY
                                                      0x2 0x9c>;      // IMX6QDL_CLK_SPBA
                                            clock-names = "core", "rxtx0", "rxtx1", "rxtx2", "rxtx3", "rxtx4", "rxtx5", "rxtx6", "rxtx7", "dma";
                                            status = "okay";
                                            pinctrl-names = "default";
                                            pinctrl-0 = <0x5>;
                                            linux,phandle = <0x39>;
                                            phandle = <0x39>;
    There are certain aspects of the device tree I haven't figured out yet, like the DMA and IRQ settings (and why Clock definitions always start with 0x02), but these shouldn't normally need to be changed on the Udoo.

    Hope this is helpful to someone... :)
  3. waltervl

    waltervl UDOOer

    Dec 12, 2015
    Likes Received:
    Thank you Fetcher!

Share This Page