blob: 68a0b82cb17ef8a6f77c3016589d823e7785ff59 [file] [log] [blame]
Laurent Pinchart81c0e3d2018-01-10 02:40:27 +02001// 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
26struct 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
43static 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
65static 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
93out_err:
94 kfree(prop->value);
95 kfree(prop->name);
96 kfree(prop);
97 return ret;
98}
99
100/* -----------------------------------------------------------------------------
101 * LVDS Overlays
102 */
103
104RCAR_DU_OF_DTB(lvds, r8a7790);
105RCAR_DU_OF_DTB(lvds, r8a7791);
106RCAR_DU_OF_DTB(lvds, r8a7793);
107RCAR_DU_OF_DTB(lvds, r8a7795);
108RCAR_DU_OF_DTB(lvds, r8a7796);
109
110static 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
119static struct of_changeset rcar_du_lvds_changeset;
120
121static 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
182done:
183 if (ret < 0)
184 of_changeset_destroy(&rcar_du_lvds_changeset);
185}
186
187struct 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
194static 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
308done:
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
319void __init rcar_du_of_init(const struct of_device_id *of_ids)
320{
321 rcar_du_of_lvds_patch(of_ids);
322}