18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * rcar_du_of.c - Legacy DT bindings compatibility 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Based on work from Jyri Sarha <jsarha@ti.com> 88c2ecf20Sopenharmony_ci * Copyright (C) 2015 Texas Instruments 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/kernel.h> 138c2ecf20Sopenharmony_ci#include <linux/of.h> 148c2ecf20Sopenharmony_ci#include <linux/of_address.h> 158c2ecf20Sopenharmony_ci#include <linux/of_fdt.h> 168c2ecf20Sopenharmony_ci#include <linux/of_graph.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include "rcar_du_crtc.h" 208c2ecf20Sopenharmony_ci#include "rcar_du_drv.h" 218c2ecf20Sopenharmony_ci#include "rcar_du_of.h" 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------------- 248c2ecf20Sopenharmony_ci * Generic Overlay Handling 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistruct rcar_du_of_overlay { 288c2ecf20Sopenharmony_ci const char *compatible; 298c2ecf20Sopenharmony_ci void *begin; 308c2ecf20Sopenharmony_ci void *end; 318c2ecf20Sopenharmony_ci}; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define RCAR_DU_OF_DTB(type, soc) \ 348c2ecf20Sopenharmony_ci extern char __dtb_rcar_du_of_##type##_##soc##_begin[]; \ 358c2ecf20Sopenharmony_ci extern char __dtb_rcar_du_of_##type##_##soc##_end[] 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define RCAR_DU_OF_OVERLAY(type, soc) \ 388c2ecf20Sopenharmony_ci { \ 398c2ecf20Sopenharmony_ci .compatible = "renesas,du-" #soc, \ 408c2ecf20Sopenharmony_ci .begin = __dtb_rcar_du_of_##type##_##soc##_begin, \ 418c2ecf20Sopenharmony_ci .end = __dtb_rcar_du_of_##type##_##soc##_end, \ 428c2ecf20Sopenharmony_ci } 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic int __init rcar_du_of_apply_overlay(const struct rcar_du_of_overlay *dtbs, 458c2ecf20Sopenharmony_ci const char *compatible) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci const struct rcar_du_of_overlay *dtb = NULL; 488c2ecf20Sopenharmony_ci unsigned int i; 498c2ecf20Sopenharmony_ci int ovcs_id; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci for (i = 0; dtbs[i].compatible; ++i) { 528c2ecf20Sopenharmony_ci if (!strcmp(dtbs[i].compatible, compatible)) { 538c2ecf20Sopenharmony_ci dtb = &dtbs[i]; 548c2ecf20Sopenharmony_ci break; 558c2ecf20Sopenharmony_ci } 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci if (!dtb) 598c2ecf20Sopenharmony_ci return -ENODEV; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci ovcs_id = 0; 628c2ecf20Sopenharmony_ci return of_overlay_fdt_apply(dtb->begin, dtb->end - dtb->begin, 638c2ecf20Sopenharmony_ci &ovcs_id); 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic int __init rcar_du_of_add_property(struct of_changeset *ocs, 678c2ecf20Sopenharmony_ci struct device_node *np, 688c2ecf20Sopenharmony_ci const char *name, const void *value, 698c2ecf20Sopenharmony_ci int length) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci struct property *prop; 728c2ecf20Sopenharmony_ci int ret = -ENOMEM; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci prop = kzalloc(sizeof(*prop), GFP_KERNEL); 758c2ecf20Sopenharmony_ci if (!prop) 768c2ecf20Sopenharmony_ci return -ENOMEM; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci prop->name = kstrdup(name, GFP_KERNEL); 798c2ecf20Sopenharmony_ci if (!prop->name) 808c2ecf20Sopenharmony_ci goto out_err; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci prop->value = kmemdup(value, length, GFP_KERNEL); 838c2ecf20Sopenharmony_ci if (!prop->value) 848c2ecf20Sopenharmony_ci goto out_err; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci of_property_set_flag(prop, OF_DYNAMIC); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci prop->length = length; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci ret = of_changeset_add_property(ocs, np, prop); 918c2ecf20Sopenharmony_ci if (!ret) 928c2ecf20Sopenharmony_ci return 0; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ciout_err: 958c2ecf20Sopenharmony_ci kfree(prop->value); 968c2ecf20Sopenharmony_ci kfree(prop->name); 978c2ecf20Sopenharmony_ci kfree(prop); 988c2ecf20Sopenharmony_ci return ret; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------------- 1028c2ecf20Sopenharmony_ci * LVDS Overlays 1038c2ecf20Sopenharmony_ci */ 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ciRCAR_DU_OF_DTB(lvds, r8a7790); 1068c2ecf20Sopenharmony_ciRCAR_DU_OF_DTB(lvds, r8a7791); 1078c2ecf20Sopenharmony_ciRCAR_DU_OF_DTB(lvds, r8a7793); 1088c2ecf20Sopenharmony_ciRCAR_DU_OF_DTB(lvds, r8a7795); 1098c2ecf20Sopenharmony_ciRCAR_DU_OF_DTB(lvds, r8a7796); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic const struct rcar_du_of_overlay rcar_du_lvds_overlays[] __initconst = { 1128c2ecf20Sopenharmony_ci RCAR_DU_OF_OVERLAY(lvds, r8a7790), 1138c2ecf20Sopenharmony_ci RCAR_DU_OF_OVERLAY(lvds, r8a7791), 1148c2ecf20Sopenharmony_ci RCAR_DU_OF_OVERLAY(lvds, r8a7793), 1158c2ecf20Sopenharmony_ci RCAR_DU_OF_OVERLAY(lvds, r8a7795), 1168c2ecf20Sopenharmony_ci RCAR_DU_OF_OVERLAY(lvds, r8a7796), 1178c2ecf20Sopenharmony_ci { /* Sentinel */ }, 1188c2ecf20Sopenharmony_ci}; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic struct of_changeset rcar_du_lvds_changeset; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic void __init rcar_du_of_lvds_patch_one(struct device_node *lvds, 1238c2ecf20Sopenharmony_ci const struct of_phandle_args *clk, 1248c2ecf20Sopenharmony_ci struct device_node *local, 1258c2ecf20Sopenharmony_ci struct device_node *remote) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci unsigned int psize; 1288c2ecf20Sopenharmony_ci unsigned int i; 1298c2ecf20Sopenharmony_ci __be32 value[4]; 1308c2ecf20Sopenharmony_ci int ret; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci /* 1338c2ecf20Sopenharmony_ci * Set the LVDS clocks property. This can't be performed by the overlay 1348c2ecf20Sopenharmony_ci * as the structure of the clock specifier has changed over time, and we 1358c2ecf20Sopenharmony_ci * don't know at compile time which binding version the system we will 1368c2ecf20Sopenharmony_ci * run on uses. 1378c2ecf20Sopenharmony_ci */ 1388c2ecf20Sopenharmony_ci if (clk->args_count >= ARRAY_SIZE(value) - 1) 1398c2ecf20Sopenharmony_ci return; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci of_changeset_init(&rcar_du_lvds_changeset); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci value[0] = cpu_to_be32(clk->np->phandle); 1448c2ecf20Sopenharmony_ci for (i = 0; i < clk->args_count; ++i) 1458c2ecf20Sopenharmony_ci value[i + 1] = cpu_to_be32(clk->args[i]); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci psize = (clk->args_count + 1) * 4; 1488c2ecf20Sopenharmony_ci ret = rcar_du_of_add_property(&rcar_du_lvds_changeset, lvds, 1498c2ecf20Sopenharmony_ci "clocks", value, psize); 1508c2ecf20Sopenharmony_ci if (ret < 0) 1518c2ecf20Sopenharmony_ci goto done; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci /* 1548c2ecf20Sopenharmony_ci * Insert the node in the OF graph: patch the LVDS ports remote-endpoint 1558c2ecf20Sopenharmony_ci * properties to point to the endpoints of the sibling nodes in the 1568c2ecf20Sopenharmony_ci * graph. This can't be performed by the overlay: on the input side the 1578c2ecf20Sopenharmony_ci * overlay would contain a phandle for the DU LVDS output port that 1588c2ecf20Sopenharmony_ci * would clash with the system DT, and on the output side the connection 1598c2ecf20Sopenharmony_ci * is board-specific. 1608c2ecf20Sopenharmony_ci */ 1618c2ecf20Sopenharmony_ci value[0] = cpu_to_be32(local->phandle); 1628c2ecf20Sopenharmony_ci value[1] = cpu_to_be32(remote->phandle); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci for (i = 0; i < 2; ++i) { 1658c2ecf20Sopenharmony_ci struct device_node *endpoint; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci endpoint = of_graph_get_endpoint_by_regs(lvds, i, 0); 1688c2ecf20Sopenharmony_ci if (!endpoint) { 1698c2ecf20Sopenharmony_ci ret = -EINVAL; 1708c2ecf20Sopenharmony_ci goto done; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci ret = rcar_du_of_add_property(&rcar_du_lvds_changeset, 1748c2ecf20Sopenharmony_ci endpoint, "remote-endpoint", 1758c2ecf20Sopenharmony_ci &value[i], sizeof(value[i])); 1768c2ecf20Sopenharmony_ci of_node_put(endpoint); 1778c2ecf20Sopenharmony_ci if (ret < 0) 1788c2ecf20Sopenharmony_ci goto done; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci ret = of_changeset_apply(&rcar_du_lvds_changeset); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cidone: 1848c2ecf20Sopenharmony_ci if (ret < 0) 1858c2ecf20Sopenharmony_ci of_changeset_destroy(&rcar_du_lvds_changeset); 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistruct lvds_of_data { 1898c2ecf20Sopenharmony_ci struct resource res; 1908c2ecf20Sopenharmony_ci struct of_phandle_args clkspec; 1918c2ecf20Sopenharmony_ci struct device_node *local; 1928c2ecf20Sopenharmony_ci struct device_node *remote; 1938c2ecf20Sopenharmony_ci}; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic void __init rcar_du_of_lvds_patch(const struct of_device_id *of_ids) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci const struct rcar_du_device_info *info; 1988c2ecf20Sopenharmony_ci const struct of_device_id *match; 1998c2ecf20Sopenharmony_ci struct lvds_of_data lvds_data[2] = { }; 2008c2ecf20Sopenharmony_ci struct device_node *lvds_node; 2018c2ecf20Sopenharmony_ci struct device_node *soc_node; 2028c2ecf20Sopenharmony_ci struct device_node *du_node; 2038c2ecf20Sopenharmony_ci char compatible[22]; 2048c2ecf20Sopenharmony_ci const char *soc_name; 2058c2ecf20Sopenharmony_ci unsigned int i; 2068c2ecf20Sopenharmony_ci int ret; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* Get the DU node and exit if not present or disabled. */ 2098c2ecf20Sopenharmony_ci du_node = of_find_matching_node_and_match(NULL, of_ids, &match); 2108c2ecf20Sopenharmony_ci if (!du_node || !of_device_is_available(du_node)) { 2118c2ecf20Sopenharmony_ci of_node_put(du_node); 2128c2ecf20Sopenharmony_ci return; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci info = match->data; 2168c2ecf20Sopenharmony_ci soc_node = of_get_parent(du_node); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci if (WARN_ON(info->num_lvds > ARRAY_SIZE(lvds_data))) 2198c2ecf20Sopenharmony_ci goto done; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci /* 2228c2ecf20Sopenharmony_ci * Skip if the LVDS nodes already exists. 2238c2ecf20Sopenharmony_ci * 2248c2ecf20Sopenharmony_ci * The nodes are searched based on the compatible string, which we 2258c2ecf20Sopenharmony_ci * construct from the SoC name found in the DU compatible string. As a 2268c2ecf20Sopenharmony_ci * match has been found we know the compatible string matches the 2278c2ecf20Sopenharmony_ci * expected format and can thus skip some of the string manipulation 2288c2ecf20Sopenharmony_ci * normal safety checks. 2298c2ecf20Sopenharmony_ci */ 2308c2ecf20Sopenharmony_ci soc_name = strchr(match->compatible, '-') + 1; 2318c2ecf20Sopenharmony_ci sprintf(compatible, "renesas,%s-lvds", soc_name); 2328c2ecf20Sopenharmony_ci lvds_node = of_find_compatible_node(NULL, NULL, compatible); 2338c2ecf20Sopenharmony_ci if (lvds_node) { 2348c2ecf20Sopenharmony_ci of_node_put(lvds_node); 2358c2ecf20Sopenharmony_ci return; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci /* 2398c2ecf20Sopenharmony_ci * Parse the DU node and store the register specifier, the clock 2408c2ecf20Sopenharmony_ci * specifier and the local and remote endpoint of the LVDS link for 2418c2ecf20Sopenharmony_ci * later use. 2428c2ecf20Sopenharmony_ci */ 2438c2ecf20Sopenharmony_ci for (i = 0; i < info->num_lvds; ++i) { 2448c2ecf20Sopenharmony_ci struct lvds_of_data *lvds = &lvds_data[i]; 2458c2ecf20Sopenharmony_ci unsigned int port; 2468c2ecf20Sopenharmony_ci char name[7]; 2478c2ecf20Sopenharmony_ci int index; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci sprintf(name, "lvds.%u", i); 2508c2ecf20Sopenharmony_ci index = of_property_match_string(du_node, "clock-names", name); 2518c2ecf20Sopenharmony_ci if (index < 0) 2528c2ecf20Sopenharmony_ci continue; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci ret = of_parse_phandle_with_args(du_node, "clocks", 2558c2ecf20Sopenharmony_ci "#clock-cells", index, 2568c2ecf20Sopenharmony_ci &lvds->clkspec); 2578c2ecf20Sopenharmony_ci if (ret < 0) 2588c2ecf20Sopenharmony_ci continue; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci port = info->routes[RCAR_DU_OUTPUT_LVDS0 + i].port; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci lvds->local = of_graph_get_endpoint_by_regs(du_node, port, 0); 2638c2ecf20Sopenharmony_ci if (!lvds->local) 2648c2ecf20Sopenharmony_ci continue; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci lvds->remote = of_graph_get_remote_endpoint(lvds->local); 2678c2ecf20Sopenharmony_ci if (!lvds->remote) 2688c2ecf20Sopenharmony_ci continue; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci index = of_property_match_string(du_node, "reg-names", name); 2718c2ecf20Sopenharmony_ci if (index < 0) 2728c2ecf20Sopenharmony_ci continue; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci of_address_to_resource(du_node, index, &lvds->res); 2758c2ecf20Sopenharmony_ci } 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci /* Parse and apply the overlay. This will resolve phandles. */ 2788c2ecf20Sopenharmony_ci ret = rcar_du_of_apply_overlay(rcar_du_lvds_overlays, 2798c2ecf20Sopenharmony_ci match->compatible); 2808c2ecf20Sopenharmony_ci if (ret < 0) 2818c2ecf20Sopenharmony_ci goto done; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci /* Patch the newly created LVDS encoder nodes. */ 2848c2ecf20Sopenharmony_ci for_each_child_of_node(soc_node, lvds_node) { 2858c2ecf20Sopenharmony_ci struct resource res; 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci if (!of_device_is_compatible(lvds_node, compatible)) 2888c2ecf20Sopenharmony_ci continue; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci /* Locate the lvds_data entry based on the resource start. */ 2918c2ecf20Sopenharmony_ci ret = of_address_to_resource(lvds_node, 0, &res); 2928c2ecf20Sopenharmony_ci if (ret < 0) 2938c2ecf20Sopenharmony_ci continue; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(lvds_data); ++i) { 2968c2ecf20Sopenharmony_ci if (lvds_data[i].res.start == res.start) 2978c2ecf20Sopenharmony_ci break; 2988c2ecf20Sopenharmony_ci } 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci if (i == ARRAY_SIZE(lvds_data)) 3018c2ecf20Sopenharmony_ci continue; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci /* Patch the LVDS encoder. */ 3048c2ecf20Sopenharmony_ci rcar_du_of_lvds_patch_one(lvds_node, &lvds_data[i].clkspec, 3058c2ecf20Sopenharmony_ci lvds_data[i].local, 3068c2ecf20Sopenharmony_ci lvds_data[i].remote); 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cidone: 3108c2ecf20Sopenharmony_ci for (i = 0; i < info->num_lvds; ++i) { 3118c2ecf20Sopenharmony_ci of_node_put(lvds_data[i].clkspec.np); 3128c2ecf20Sopenharmony_ci of_node_put(lvds_data[i].local); 3138c2ecf20Sopenharmony_ci of_node_put(lvds_data[i].remote); 3148c2ecf20Sopenharmony_ci } 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci of_node_put(soc_node); 3178c2ecf20Sopenharmony_ci of_node_put(du_node); 3188c2ecf20Sopenharmony_ci} 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_civoid __init rcar_du_of_init(const struct of_device_id *of_ids) 3218c2ecf20Sopenharmony_ci{ 3228c2ecf20Sopenharmony_ci rcar_du_of_lvds_patch(of_ids); 3238c2ecf20Sopenharmony_ci} 324