linux/drivers/gpu/drm/rcar-du/rcar_du_of.c
<<
>>
Prefs
   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
  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}
 323