1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2025 Bootlin 4 * Author: Kory Maincent <kory.maincent@bootlin.com> 5 * 6 * To support the legacy "ti,tilcdc,panel" binding, the devicetree has to 7 * be transformed to the new panel-dpi binding with the endpoint associated. 8 */ 9 10 #include <linux/kernel.h> 11 #include <linux/of.h> 12 #include <linux/of_fdt.h> 13 #include <linux/slab.h> 14 15 /* Embedded dtbo symbols created by cmd_wrap_S_dtb in scripts/Makefile.lib */ 16 extern char __dtbo_tilcdc_panel_legacy_begin[]; 17 extern char __dtbo_tilcdc_panel_legacy_end[]; 18 19 static int __init 20 tilcdc_panel_update_prop(struct of_changeset *ocs, struct device_node *node, 21 char *name, void *val, int length) 22 { 23 struct property *prop; 24 25 prop = kzalloc(sizeof(*prop), GFP_KERNEL); 26 if (!prop) 27 return -ENOMEM; 28 29 prop->name = kstrdup(name, GFP_KERNEL); 30 prop->length = length; 31 prop->value = kmemdup(val, length, GFP_KERNEL); 32 if (!prop->name || !prop->value) { 33 kfree(prop->name); 34 kfree(prop->value); 35 kfree(prop); 36 return -ENOMEM; 37 } 38 39 return of_changeset_update_property(ocs, node, prop); 40 } 41 42 static int __init tilcdc_panel_copy_props(struct device_node *old_panel, 43 struct device_node *new_panel) 44 { 45 struct device_node *old_timing __free(device_node) = NULL; 46 struct device_node *new_timing __free(device_node) = NULL; 47 struct device_node *panel_info __free(device_node) = NULL; 48 struct device_node *child __free(device_node) = NULL; 49 u32 invert_pxl_clk = 0, sync_edge = 0; 50 struct of_changeset ocs; 51 struct property *prop; 52 int ret; 53 54 child = of_get_child_by_name(old_panel, "display-timings"); 55 if (!child) 56 return -EINVAL; 57 58 /* The default display timing is the one specified as native-mode. 59 * If no native-mode is specified then the first node is assumed 60 * to be the native mode. 61 */ 62 old_timing = of_parse_phandle(child, "native-mode", 0); 63 if (!old_timing) { 64 old_timing = of_get_next_child(child, NULL); 65 if (!old_timing) 66 return -EINVAL; 67 } 68 69 panel_info = of_get_child_by_name(old_panel, "panel-info"); 70 if (!panel_info) 71 return -EINVAL; 72 73 of_changeset_init(&ocs); 74 75 /* Copy all panel properties to the new panel node */ 76 for_each_property_of_node(old_panel, prop) { 77 if (!strncmp(prop->name, "compatible", sizeof("compatible"))) 78 continue; 79 80 ret = tilcdc_panel_update_prop(&ocs, new_panel, prop->name, 81 prop->value, prop->length); 82 if (ret) 83 goto destroy_ocs; 84 } 85 86 new_timing = of_changeset_create_node(&ocs, new_panel, "panel-timing"); 87 if (!new_timing) { 88 ret = -ENODEV; 89 goto destroy_ocs; 90 } 91 92 /* Copy all panel timing properties to the new panel node */ 93 for_each_property_of_node(old_timing, prop) { 94 ret = tilcdc_panel_update_prop(&ocs, new_timing, prop->name, 95 prop->value, prop->length); 96 if (ret) 97 goto destroy_ocs; 98 } 99 100 /* Looked only for these two parameter as all the other are always 101 * set to default and not related to common DRM properties. 102 */ 103 of_property_read_u32(panel_info, "invert-pxl-clk", &invert_pxl_clk); 104 of_property_read_u32(panel_info, "sync-edge", &sync_edge); 105 106 if (!invert_pxl_clk) { 107 ret = tilcdc_panel_update_prop(&ocs, new_timing, "pixelclk-active", 108 &(__be32){cpu_to_be32(1)}, sizeof(__be32)); 109 if (ret) 110 goto destroy_ocs; 111 } 112 113 if (!sync_edge) { 114 ret = tilcdc_panel_update_prop(&ocs, new_timing, "syncclk-active", 115 &(__be32){cpu_to_be32(1)}, sizeof(__be32)); 116 if (ret) 117 goto destroy_ocs; 118 } 119 120 /* Remove compatible property to avoid any driver compatible match */ 121 of_changeset_remove_property(&ocs, old_panel, 122 of_find_property(old_panel, "compatible", NULL)); 123 124 of_changeset_apply(&ocs); 125 return 0; 126 127 destroy_ocs: 128 of_changeset_destroy(&ocs); 129 return ret; 130 } 131 132 static const struct of_device_id tilcdc_panel_of_match[] __initconst = { 133 { .compatible = "ti,tilcdc,panel", }, 134 {}, 135 }; 136 137 static const struct of_device_id tilcdc_of_match[] __initconst = { 138 { .compatible = "ti,am33xx-tilcdc", }, 139 { .compatible = "ti,da850-tilcdc", }, 140 {}, 141 }; 142 143 static int __init tilcdc_panel_legacy_init(void) 144 { 145 struct device_node *new_panel __free(device_node) = NULL; 146 struct device_node *panel __free(device_node) = NULL; 147 struct device_node *lcdc __free(device_node) = NULL; 148 void *dtbo_start; 149 u32 dtbo_size; 150 int ovcs_id; 151 int ret; 152 153 lcdc = of_find_matching_node(NULL, tilcdc_of_match); 154 panel = of_find_matching_node(NULL, tilcdc_panel_of_match); 155 156 if (!of_device_is_available(panel) || 157 !of_device_is_available(lcdc)) 158 return 0; 159 160 dtbo_start = __dtbo_tilcdc_panel_legacy_begin; 161 dtbo_size = __dtbo_tilcdc_panel_legacy_end - 162 __dtbo_tilcdc_panel_legacy_begin; 163 164 ret = of_overlay_fdt_apply(dtbo_start, dtbo_size, &ovcs_id, NULL); 165 if (ret) 166 return ret; 167 168 new_panel = of_find_node_by_name(NULL, "tilcdc-panel-dpi"); 169 if (!new_panel) { 170 ret = -ENODEV; 171 goto overlay_remove; 172 } 173 174 ret = tilcdc_panel_copy_props(panel, new_panel); 175 if (ret) 176 goto overlay_remove; 177 178 return 0; 179 180 overlay_remove: 181 of_overlay_remove(&ovcs_id); 182 return ret; 183 } 184 185 subsys_initcall(tilcdc_panel_legacy_init); 186