Skip to content

Commit 81c0e3d

Browse files
author
Laurent Pinchart
committed
drm: rcar-du: Fix legacy DT to create LVDS encoder nodes
The internal LVDS encoders now have their own DT bindings. Before switching the driver infrastructure to those new bindings, implement backward-compatibility through live DT patching. Patching is disabled and will be enabled along with support for the new DT bindings in the DU driver. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se> Reviewed-by: Frank Rowand <frank.rowand@sony.com>
1 parent 6d2ca85 commit 81c0e3d

9 files changed

+626
-1
lines changed

drivers/gpu/drm/rcar-du/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ config DRM_RCAR_LVDS
2222
bool "R-Car DU LVDS Encoder Support"
2323
depends on DRM_RCAR_DU
2424
select DRM_PANEL
25+
select OF_FLATTREE
26+
select OF_OVERLAY
2527
help
2628
Enable support for the R-Car Display Unit embedded LVDS encoders.
2729

drivers/gpu/drm/rcar-du/Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ rcar-du-drm-y := rcar_du_crtc.o \
88
rcar_du_plane.o
99

1010
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o
11-
11+
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \
12+
rcar_du_of_lvds_r8a7790.dtb.o \
13+
rcar_du_of_lvds_r8a7791.dtb.o \
14+
rcar_du_of_lvds_r8a7793.dtb.o \
15+
rcar_du_of_lvds_r8a7795.dtb.o \
16+
rcar_du_of_lvds_r8a7796.dtb.o
1217
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
1318

1419
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o

drivers/gpu/drm/rcar-du/rcar_du_of.c

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* rcar_du_of.c - Legacy DT bindings compatibility
4+
*
5+
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
6+
*
7+
* Based on work from Jyri Sarha <jsarha@ti.com>
8+
* Copyright (C) 2015 Texas Instruments
9+
*/
10+
11+
#include <linux/init.h>
12+
#include <linux/kernel.h>
13+
#include <linux/of.h>
14+
#include <linux/of_address.h>
15+
#include <linux/of_fdt.h>
16+
#include <linux/of_graph.h>
17+
#include <linux/slab.h>
18+
19+
#include "rcar_du_crtc.h"
20+
#include "rcar_du_drv.h"
21+
22+
/* -----------------------------------------------------------------------------
23+
* Generic Overlay Handling
24+
*/
25+
26+
struct rcar_du_of_overlay {
27+
const char *compatible;
28+
void *begin;
29+
void *end;
30+
};
31+
32+
#define RCAR_DU_OF_DTB(type, soc) \
33+
extern char __dtb_rcar_du_of_##type##_##soc##_begin[]; \
34+
extern char __dtb_rcar_du_of_##type##_##soc##_end[]
35+
36+
#define RCAR_DU_OF_OVERLAY(type, soc) \
37+
{ \
38+
.compatible = "renesas,du-" #soc, \
39+
.begin = __dtb_rcar_du_of_##type##_##soc##_begin, \
40+
.end = __dtb_rcar_du_of_##type##_##soc##_end, \
41+
}
42+
43+
static int __init rcar_du_of_apply_overlay(const struct rcar_du_of_overlay *dtbs,
44+
const char *compatible)
45+
{
46+
const struct rcar_du_of_overlay *dtb = NULL;
47+
unsigned int i;
48+
int ovcs_id;
49+
50+
for (i = 0; dtbs[i].compatible; ++i) {
51+
if (!strcmp(dtbs[i].compatible, compatible)) {
52+
dtb = &dtbs[i];
53+
break;
54+
}
55+
}
56+
57+
if (!dtb)
58+
return -ENODEV;
59+
60+
ovcs_id = 0;
61+
return of_overlay_fdt_apply(dtb->begin, dtb->end - dtb->begin,
62+
&ovcs_id);
63+
}
64+
65+
static int __init rcar_du_of_add_property(struct of_changeset *ocs,
66+
struct device_node *np,
67+
const char *name, const void *value,
68+
int length)
69+
{
70+
struct property *prop;
71+
int ret = -ENOMEM;
72+
73+
prop = kzalloc(sizeof(*prop), GFP_KERNEL);
74+
if (!prop)
75+
return -ENOMEM;
76+
77+
prop->name = kstrdup(name, GFP_KERNEL);
78+
if (!prop->name)
79+
goto out_err;
80+
81+
prop->value = kmemdup(value, length, GFP_KERNEL);
82+
if (!prop->value)
83+
goto out_err;
84+
85+
of_property_set_flag(prop, OF_DYNAMIC);
86+
87+
prop->length = length;
88+
89+
ret = of_changeset_add_property(ocs, np, prop);
90+
if (!ret)
91+
return 0;
92+
93+
out_err:
94+
kfree(prop->value);
95+
kfree(prop->name);
96+
kfree(prop);
97+
return ret;
98+
}
99+
100+
/* -----------------------------------------------------------------------------
101+
* LVDS Overlays
102+
*/
103+
104+
RCAR_DU_OF_DTB(lvds, r8a7790);
105+
RCAR_DU_OF_DTB(lvds, r8a7791);
106+
RCAR_DU_OF_DTB(lvds, r8a7793);
107+
RCAR_DU_OF_DTB(lvds, r8a7795);
108+
RCAR_DU_OF_DTB(lvds, r8a7796);
109+
110+
static const struct rcar_du_of_overlay rcar_du_lvds_overlays[] __initconst = {
111+
RCAR_DU_OF_OVERLAY(lvds, r8a7790),
112+
RCAR_DU_OF_OVERLAY(lvds, r8a7791),
113+
RCAR_DU_OF_OVERLAY(lvds, r8a7793),
114+
RCAR_DU_OF_OVERLAY(lvds, r8a7795),
115+
RCAR_DU_OF_OVERLAY(lvds, r8a7796),
116+
{ /* Sentinel */ },
117+
};
118+
119+
static struct of_changeset rcar_du_lvds_changeset;
120+
121+
static void __init rcar_du_of_lvds_patch_one(struct device_node *lvds,
122+
const struct of_phandle_args *clk,
123+
struct device_node *local,
124+
struct device_node *remote)
125+
{
126+
unsigned int psize;
127+
unsigned int i;
128+
__be32 value[4];
129+
int ret;
130+
131+
/*
132+
* Set the LVDS clocks property. This can't be performed by the overlay
133+
* as the structure of the clock specifier has changed over time, and we
134+
* don't know at compile time which binding version the system we will
135+
* run on uses.
136+
*/
137+
if (clk->args_count >= ARRAY_SIZE(value) - 1)
138+
return;
139+
140+
of_changeset_init(&rcar_du_lvds_changeset);
141+
142+
value[0] = cpu_to_be32(clk->np->phandle);
143+
for (i = 0; i < clk->args_count; ++i)
144+
value[i + 1] = cpu_to_be32(clk->args[i]);
145+
146+
psize = (clk->args_count + 1) * 4;
147+
ret = rcar_du_of_add_property(&rcar_du_lvds_changeset, lvds,
148+
"clocks", value, psize);
149+
if (ret < 0)
150+
goto done;
151+
152+
/*
153+
* Insert the node in the OF graph: patch the LVDS ports remote-endpoint
154+
* properties to point to the endpoints of the sibling nodes in the
155+
* graph. This can't be performed by the overlay: on the input side the
156+
* overlay would contain a phandle for the DU LVDS output port that
157+
* would clash with the system DT, and on the output side the connection
158+
* is board-specific.
159+
*/
160+
value[0] = cpu_to_be32(local->phandle);
161+
value[1] = cpu_to_be32(remote->phandle);
162+
163+
for (i = 0; i < 2; ++i) {
164+
struct device_node *endpoint;
165+
166+
endpoint = of_graph_get_endpoint_by_regs(lvds, i, 0);
167+
if (!endpoint) {
168+
ret = -EINVAL;
169+
goto done;
170+
}
171+
172+
ret = rcar_du_of_add_property(&rcar_du_lvds_changeset,
173+
endpoint, "remote-endpoint",
174+
&value[i], sizeof(value[i]));
175+
of_node_put(endpoint);
176+
if (ret < 0)
177+
goto done;
178+
}
179+
180+
ret = of_changeset_apply(&rcar_du_lvds_changeset);
181+
182+
done:
183+
if (ret < 0)
184+
of_changeset_destroy(&rcar_du_lvds_changeset);
185+
}
186+
187+
struct lvds_of_data {
188+
struct resource res;
189+
struct of_phandle_args clkspec;
190+
struct device_node *local;
191+
struct device_node *remote;
192+
};
193+
194+
static void __init rcar_du_of_lvds_patch(const struct of_device_id *of_ids)
195+
{
196+
const struct rcar_du_device_info *info;
197+
const struct of_device_id *match;
198+
struct lvds_of_data lvds_data[2] = { };
199+
struct device_node *lvds_node;
200+
struct device_node *soc_node;
201+
struct device_node *du_node;
202+
char compatible[22];
203+
const char *soc_name;
204+
unsigned int i;
205+
int ret;
206+
207+
/* Get the DU node and exit if not present or disabled. */
208+
du_node = of_find_matching_node_and_match(NULL, of_ids, &match);
209+
if (!du_node || !of_device_is_available(du_node)) {
210+
of_node_put(du_node);
211+
return;
212+
}
213+
214+
info = match->data;
215+
soc_node = of_get_parent(du_node);
216+
217+
if (WARN_ON(info->num_lvds > ARRAY_SIZE(lvds_data)))
218+
goto done;
219+
220+
/*
221+
* Skip if the LVDS nodes already exists.
222+
*
223+
* The nodes are searched based on the compatible string, which we
224+
* construct from the SoC name found in the DU compatible string. As a
225+
* match has been found we know the compatible string matches the
226+
* expected format and can thus skip some of the string manipulation
227+
* normal safety checks.
228+
*/
229+
soc_name = strchr(match->compatible, '-') + 1;
230+
sprintf(compatible, "renesas,%s-lvds", soc_name);
231+
lvds_node = of_find_compatible_node(NULL, NULL, compatible);
232+
if (lvds_node) {
233+
of_node_put(lvds_node);
234+
return;
235+
}
236+
237+
/*
238+
* Parse the DU node and store the register specifier, the clock
239+
* specifier and the local and remote endpoint of the LVDS link for
240+
* later use.
241+
*/
242+
for (i = 0; i < info->num_lvds; ++i) {
243+
struct lvds_of_data *lvds = &lvds_data[i];
244+
unsigned int port;
245+
char name[7];
246+
int index;
247+
248+
sprintf(name, "lvds.%u", i);
249+
index = of_property_match_string(du_node, "clock-names", name);
250+
if (index < 0)
251+
continue;
252+
253+
ret = of_parse_phandle_with_args(du_node, "clocks",
254+
"#clock-cells", index,
255+
&lvds->clkspec);
256+
if (ret < 0)
257+
continue;
258+
259+
port = info->routes[RCAR_DU_OUTPUT_LVDS0 + i].port;
260+
261+
lvds->local = of_graph_get_endpoint_by_regs(du_node, port, 0);
262+
if (!lvds->local)
263+
continue;
264+
265+
lvds->remote = of_graph_get_remote_endpoint(lvds->local);
266+
if (!lvds->remote)
267+
continue;
268+
269+
index = of_property_match_string(du_node, "reg-names", name);
270+
if (index < 0)
271+
continue;
272+
273+
of_address_to_resource(du_node, index, &lvds->res);
274+
}
275+
276+
/* Parse and apply the overlay. This will resolve phandles. */
277+
ret = rcar_du_of_apply_overlay(rcar_du_lvds_overlays,
278+
match->compatible);
279+
if (ret < 0)
280+
goto done;
281+
282+
/* Patch the newly created LVDS encoder nodes. */
283+
for_each_child_of_node(soc_node, lvds_node) {
284+
struct resource res;
285+
286+
if (!of_device_is_compatible(lvds_node, compatible))
287+
continue;
288+
289+
/* Locate the lvds_data entry based on the resource start. */
290+
ret = of_address_to_resource(lvds_node, 0, &res);
291+
if (ret < 0)
292+
continue;
293+
294+
for (i = 0; i < ARRAY_SIZE(lvds_data); ++i) {
295+
if (lvds_data[i].res.start == res.start)
296+
break;
297+
}
298+
299+
if (i == ARRAY_SIZE(lvds_data))
300+
continue;
301+
302+
/* Patch the LVDS encoder. */
303+
rcar_du_of_lvds_patch_one(lvds_node, &lvds_data[i].clkspec,
304+
lvds_data[i].local,
305+
lvds_data[i].remote);
306+
}
307+
308+
done:
309+
for (i = 0; i < info->num_lvds; ++i) {
310+
of_node_put(lvds_data[i].clkspec.np);
311+
of_node_put(lvds_data[i].local);
312+
of_node_put(lvds_data[i].remote);
313+
}
314+
315+
of_node_put(soc_node);
316+
of_node_put(du_node);
317+
}
318+
319+
void __init rcar_du_of_init(const struct of_device_id *of_ids)
320+
{
321+
rcar_du_of_lvds_patch(of_ids);
322+
}

drivers/gpu/drm/rcar-du/rcar_du_of.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
/*
3+
* rcar_du_of.h - Legacy DT bindings compatibility
4+
*
5+
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
6+
*/
7+
#ifndef __RCAR_DU_OF_H__
8+
#define __RCAR_DU_OF_H__
9+
10+
#include <linux/init.h>
11+
12+
struct of_device_id;
13+
14+
#ifdef CONFIG_DRM_RCAR_LVDS
15+
void __init rcar_du_of_init(const struct of_device_id *of_ids);
16+
#else
17+
static inline void rcar_du_of_init(const struct of_device_id *of_ids) { }
18+
#endif /* CONFIG_DRM_RCAR_LVDS */
19+
20+
#endif /* __RCAR_DU_OF_H__ */

0 commit comments

Comments
 (0)