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