1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
// 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);
|