root/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2025 Bootlin
 * Author: Kory Maincent <kory.maincent@bootlin.com>
 *
 * To support the legacy "ti,tilcdc,panel" binding, the devicetree has to
 * be transformed to the new panel-dpi binding with the endpoint associated.
 */

#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/slab.h>

/* Embedded dtbo symbols created by cmd_wrap_S_dtb in scripts/Makefile.lib */
extern char __dtbo_tilcdc_panel_legacy_begin[];
extern char __dtbo_tilcdc_panel_legacy_end[];

static int __init
tilcdc_panel_update_prop(struct of_changeset *ocs, struct device_node *node,
                         char *name, void *val, int length)
{
        struct property *prop;

        prop = kzalloc(sizeof(*prop), GFP_KERNEL);
        if (!prop)
                return -ENOMEM;

        prop->name = kstrdup(name, GFP_KERNEL);
        prop->length = length;
        prop->value = kmemdup(val, length, GFP_KERNEL);
        if (!prop->name || !prop->value) {
                kfree(prop->name);
                kfree(prop->value);
                kfree(prop);
                return -ENOMEM;
        }

        return of_changeset_update_property(ocs, node, prop);
}

static int __init tilcdc_panel_copy_props(struct device_node *old_panel,
                                          struct device_node *new_panel)
{
        struct device_node *old_timing __free(device_node) = NULL;
        struct device_node *new_timing __free(device_node) = NULL;
        struct device_node *panel_info __free(device_node) = NULL;
        struct device_node *child __free(device_node) = NULL;
        u32 invert_pxl_clk = 0, sync_edge = 0;
        struct of_changeset ocs;
        struct property *prop;
        int ret;

        child = of_get_child_by_name(old_panel, "display-timings");
        if (!child)
                return -EINVAL;

        /* The default display timing is the one specified as native-mode.
         * If no native-mode is specified then the first node is assumed
         * to be the native mode.
         */
        old_timing = of_parse_phandle(child, "native-mode", 0);
        if (!old_timing) {
                old_timing = of_get_next_child(child, NULL);
                if (!old_timing)
                        return -EINVAL;
        }

        panel_info = of_get_child_by_name(old_panel, "panel-info");
        if (!panel_info)
                return -EINVAL;

        of_changeset_init(&ocs);

        /* Copy all panel properties to the new panel node */
        for_each_property_of_node(old_panel, prop) {
                if (!strncmp(prop->name, "compatible", sizeof("compatible")))
                        continue;

                ret = tilcdc_panel_update_prop(&ocs, new_panel, prop->name,
                                               prop->value, prop->length);
                if (ret)
                        goto destroy_ocs;
        }

        new_timing = of_changeset_create_node(&ocs, new_panel, "panel-timing");
        if (!new_timing) {
                ret = -ENODEV;
                goto destroy_ocs;
        }

        /* Copy all panel timing properties to the new panel node */
        for_each_property_of_node(old_timing, prop) {
                ret = tilcdc_panel_update_prop(&ocs, new_timing, prop->name,
                                               prop->value, prop->length);
                if (ret)
                        goto destroy_ocs;
        }

        /* Looked only for these two parameter as all the other are always
         * set to default and not related to common DRM properties.
         */
        of_property_read_u32(panel_info, "invert-pxl-clk", &invert_pxl_clk);
        of_property_read_u32(panel_info, "sync-edge", &sync_edge);

        if (!invert_pxl_clk) {
                ret = tilcdc_panel_update_prop(&ocs, new_timing, "pixelclk-active",
                                               &(__be32){cpu_to_be32(1)}, sizeof(__be32));
                if (ret)
                        goto destroy_ocs;
        }

        if (!sync_edge) {
                ret = tilcdc_panel_update_prop(&ocs, new_timing, "syncclk-active",
                                               &(__be32){cpu_to_be32(1)}, sizeof(__be32));
                if (ret)
                        goto destroy_ocs;
        }

        /* Remove compatible property to avoid any driver compatible match */
        of_changeset_remove_property(&ocs, old_panel,
                                     of_find_property(old_panel, "compatible", NULL));

        of_changeset_apply(&ocs);
        return 0;

destroy_ocs:
        of_changeset_destroy(&ocs);
        return ret;
}

static const struct of_device_id tilcdc_panel_of_match[] __initconst = {
        { .compatible = "ti,tilcdc,panel", },
        {},
};

static const struct of_device_id tilcdc_of_match[] __initconst = {
        { .compatible = "ti,am33xx-tilcdc", },
        { .compatible = "ti,da850-tilcdc", },
        {},
};

static int __init tilcdc_panel_legacy_init(void)
{
        struct device_node *new_panel __free(device_node) = NULL;
        struct device_node *panel __free(device_node) = NULL;
        struct device_node *lcdc __free(device_node) = NULL;
        void *dtbo_start;
        u32 dtbo_size;
        int ovcs_id;
        int ret;

        lcdc = of_find_matching_node(NULL, tilcdc_of_match);
        panel = of_find_matching_node(NULL, tilcdc_panel_of_match);

        if (!of_device_is_available(panel) ||
            !of_device_is_available(lcdc))
                return 0;

        dtbo_start = __dtbo_tilcdc_panel_legacy_begin;
        dtbo_size = __dtbo_tilcdc_panel_legacy_end -
                    __dtbo_tilcdc_panel_legacy_begin;

        ret = of_overlay_fdt_apply(dtbo_start, dtbo_size, &ovcs_id, NULL);
        if (ret)
                return ret;

        new_panel = of_find_node_by_name(NULL, "tilcdc-panel-dpi");
        if (!new_panel) {
                ret = -ENODEV;
                goto overlay_remove;
        }

        ret = tilcdc_panel_copy_props(panel, new_panel);
        if (ret)
                goto overlay_remove;

        return 0;

overlay_remove:
        of_overlay_remove(&ovcs_id);
        return ret;
}

subsys_initcall(tilcdc_panel_legacy_init);