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
tilcdc_panel_update_prop(struct of_changeset * ocs,struct device_node * node,char * name,void * val,int length)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_obj(*prop);
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
tilcdc_panel_copy_props(struct device_node * old_panel,struct device_node * new_panel)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
tilcdc_panel_legacy_init(void)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