Search code examples
linux-kernelembedded-linuxdevice-treeimx6barebox

Add two TFT displays to device tree


I'm working on a embedded device with Yocto Linux. Recently the display type was replaced. I was able to make the changes in the kernel and device tree. But now I'm suffering to enable both types in the same image.

How can I add two panel-lcd definitions with status="disabled" and activate one OR the other by calling of_fixup_status() in Barebox?

My dtsi looks like this:

/ {
    panel-lcd-tn {
        compatible = "powertip,ph320240t023_iha";
        backlight = <&backlight_peb>;
        status = "disabled";

        port {
            panel_in: endpoint {
                remote-endpoint = <&display_out>;
            };
        };
    };

    panel-lcd-ips {
        compatible = "powertip,ph320240t028_zha";
        backlight = <&backlight_peb>;
        status = "disabled";

        port {
            panel_in: endpoint {
                remote-endpoint = <&display_out>;
            };
        };
    };
...
...
...
};

&lcdif {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_lcdif_dat>;
    status = "disabled";

    port {
        display_out: endpoint {
            remote-endpoint = <&panel_in>;
        };
    };
};

The compiler tells me

Duplicate label 'panel_in'

Note: The only difference is the compatible string.

I've tried to rename panel_in to panel_in1 and panel_in2, but then the reference to this label is no longer valid at &lcdif. So I've doubled this one a well. The DT compiles then, but the display does not work any more. On bootup the error

mxsfb 21c8000.lcdif: Cannot connect bridge: -19

appears. Does anybody have a clue how to solve it?


Solution

  • In general, having two device tree nodes and enabling one or the other is a good approach. Panels are a bit special though, because they use the device tree graph binding, where the panel points at the display controller (or an in-between bridge) and the display points back. Duplicating the panel and choosing one or the other thus means that you also need to fix up the link in the display controller node. That's doable, but it's easier to just keep a single panel node and fix up only the compatible dynamically.

    Provided your kernel device tree looks like this:

    / {
       aliases {
            display = &display;
       };
        
       display: panel-lcd {
            compatible = "powertip,ph320240t023_iha";
            backlight = <&backlight_peb>;
    
            port {
                panel_in: endpoint {
                    remote-endpoint = <&display_out>;
                };
            };
        };
    };
    

    It's then quite trivial to rewrite the kernel device tree in barebox:

    #!/bin/sh
    of_property -fs display compatible "powertip,ph320240t028_zha"
    

    -f registers a fixup, i.e. the change is applied to the kernel device tree on boot and not to the barebox device tree. -s means to set the property. display is the alias we choose (We could also have used /panel-lcd).

    You can add this script into your barebox environment init directory, so it's automatically sourced on startup.

    You can also achieve the same in C board code. This can be better than a script, when you e.g. disable the shell interpreter in a secure booting system, if you do more complex fixups or if you need to parse some more complex EEPROM format to determine what panel you have. Here's an example:

    #include <of.h>
    #include <linux/printk.h>
    #include <driver.h>
    #include <init.h>
    
    struct board_variant {
            const char *panel_compatible;
    };
    
    static int my_board_display_fixup(struct device_node *root, void *_variant)
    {
            const struct board_variant *variant = _variant;
            struct device_node *display;
    
            display = of_find_node_by_alias(root, "display");
            if (!display) {
                    pr_err("Cannot find node display\n");
                    return -EINVAL;
            }
    
            return of_property_write_string(display, "compatible",
                                            variant->panel_compatible);
    }
    
    static int my_customboard_probe(struct device *dev)
    {
            /*
             * will be used at device tree fixup time, so it needs to
             * be either dynamically allocated or static storage
             */
            static struct board_variant variant;
    
            /* read EEPROM or something to decide dynamically */
            variant.panel_compatible = "powertip,ph320240t028_zha";
    
            return of_register_fixup(my_board_display_fixup, &variant);
    }
    
    static struct of_device_id board_dt_ids[] = {
            { .compatible = "my,imx6q-customboard", },
            { /* sentinel */ }
    };
    
    static struct driver my_customboard_driver = {
            .name = "my-customboard",
            .probe = my_customboard_probe,
            .of_compatible = board_dt_ids,
    };
    coredevice_platform_driver(my_customboard_driver);
    

    This code is usually placed in your board code directory, e.g. arch/arm/boards/my-customboard/board.c. my,imx6q-customboard is the compatible of your board (as written in the barebox device tree).

    Finally, barebox also supports applying device tree overlays, which it will translate into fixups under the hood. For you simple case, explicitly using a fixup may be the easier way though.