xref: /linux/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c (revision 4a57e0913e8c7fff407e97909f4ae48caa84d612)
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