linux/drivers/gpu/drm/tilcdc/tilcdc_external.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (C) 2015 Texas Instruments
   4 * Author: Jyri Sarha <jsarha@ti.com>
   5 */
   6
   7#include <linux/component.h>
   8#include <linux/of_graph.h>
   9#include <drm/drm_atomic_helper.h>
  10#include <drm/drm_of.h>
  11
  12#include "tilcdc_drv.h"
  13#include "tilcdc_external.h"
  14
  15static const struct tilcdc_panel_info panel_info_tda998x = {
  16                .ac_bias                = 255,
  17                .ac_bias_intrpt         = 0,
  18                .dma_burst_sz           = 16,
  19                .bpp                    = 16,
  20                .fdd                    = 0x80,
  21                .tft_alt_mode           = 0,
  22                .invert_pxl_clk         = 1,
  23                .sync_edge              = 1,
  24                .sync_ctrl              = 1,
  25                .raster_order           = 0,
  26};
  27
  28static const struct tilcdc_panel_info panel_info_default = {
  29                .ac_bias                = 255,
  30                .ac_bias_intrpt         = 0,
  31                .dma_burst_sz           = 16,
  32                .bpp                    = 16,
  33                .fdd                    = 0x80,
  34                .tft_alt_mode           = 0,
  35                .sync_edge              = 0,
  36                .sync_ctrl              = 1,
  37                .raster_order           = 0,
  38};
  39
  40static int tilcdc_external_mode_valid(struct drm_connector *connector,
  41                                      struct drm_display_mode *mode)
  42{
  43        struct tilcdc_drm_private *priv = connector->dev->dev_private;
  44        int ret;
  45
  46        ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
  47        if (ret != MODE_OK)
  48                return ret;
  49
  50        BUG_ON(priv->external_connector != connector);
  51        BUG_ON(!priv->connector_funcs);
  52
  53        /* If the connector has its own mode_valid call it. */
  54        if (!IS_ERR(priv->connector_funcs) &&
  55            priv->connector_funcs->mode_valid)
  56                return priv->connector_funcs->mode_valid(connector, mode);
  57
  58        return MODE_OK;
  59}
  60
  61static int tilcdc_add_external_connector(struct drm_device *dev,
  62                                         struct drm_connector *connector)
  63{
  64        struct tilcdc_drm_private *priv = dev->dev_private;
  65        struct drm_connector_helper_funcs *connector_funcs;
  66
  67        /* There should never be more than one connector */
  68        if (WARN_ON(priv->external_connector))
  69                return -EINVAL;
  70
  71        priv->external_connector = connector;
  72        connector_funcs = devm_kzalloc(dev->dev, sizeof(*connector_funcs),
  73                                       GFP_KERNEL);
  74        if (!connector_funcs)
  75                return -ENOMEM;
  76
  77        /* connector->helper_private contains always struct
  78         * connector_helper_funcs pointer. For tilcdc crtc to have a
  79         * say if a specific mode is Ok, we need to install our own
  80         * helper functions. In our helper functions we copy
  81         * everything else but use our own mode_valid() (above).
  82         */
  83        if (connector->helper_private) {
  84                priv->connector_funcs = connector->helper_private;
  85                *connector_funcs = *priv->connector_funcs;
  86        } else {
  87                priv->connector_funcs = ERR_PTR(-ENOENT);
  88        }
  89        connector_funcs->mode_valid = tilcdc_external_mode_valid;
  90        drm_connector_helper_add(connector, connector_funcs);
  91
  92        dev_dbg(dev->dev, "External connector '%s' connected\n",
  93                connector->name);
  94
  95        return 0;
  96}
  97
  98static
  99struct drm_connector *tilcdc_encoder_find_connector(struct drm_device *ddev,
 100                                                    struct drm_encoder *encoder)
 101{
 102        struct drm_connector *connector;
 103
 104        list_for_each_entry(connector, &ddev->mode_config.connector_list, head) {
 105                if (drm_connector_has_possible_encoder(connector, encoder))
 106                        return connector;
 107        }
 108
 109        dev_err(ddev->dev, "No connector found for %s encoder (id %d)\n",
 110                encoder->name, encoder->base.id);
 111
 112        return NULL;
 113}
 114
 115int tilcdc_add_component_encoder(struct drm_device *ddev)
 116{
 117        struct tilcdc_drm_private *priv = ddev->dev_private;
 118        struct drm_connector *connector;
 119        struct drm_encoder *encoder;
 120
 121        list_for_each_entry(encoder, &ddev->mode_config.encoder_list, head)
 122                if (encoder->possible_crtcs & (1 << priv->crtc->index))
 123                        break;
 124
 125        if (!encoder) {
 126                dev_err(ddev->dev, "%s: No suitable encoder found\n", __func__);
 127                return -ENODEV;
 128        }
 129
 130        connector = tilcdc_encoder_find_connector(ddev, encoder);
 131
 132        if (!connector)
 133                return -ENODEV;
 134
 135        /* Only tda998x is supported at the moment. */
 136        tilcdc_crtc_set_simulate_vesa_sync(priv->crtc, true);
 137        tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_tda998x);
 138
 139        return tilcdc_add_external_connector(ddev, connector);
 140}
 141
 142void tilcdc_remove_external_device(struct drm_device *dev)
 143{
 144        struct tilcdc_drm_private *priv = dev->dev_private;
 145
 146        /* Restore the original helper functions, if any. */
 147        if (IS_ERR(priv->connector_funcs))
 148                drm_connector_helper_add(priv->external_connector, NULL);
 149        else if (priv->connector_funcs)
 150                drm_connector_helper_add(priv->external_connector,
 151                                         priv->connector_funcs);
 152}
 153
 154static const struct drm_encoder_funcs tilcdc_external_encoder_funcs = {
 155        .destroy        = drm_encoder_cleanup,
 156};
 157
 158static
 159int tilcdc_attach_bridge(struct drm_device *ddev, struct drm_bridge *bridge)
 160{
 161        struct tilcdc_drm_private *priv = ddev->dev_private;
 162        struct drm_connector *connector;
 163        int ret;
 164
 165        priv->external_encoder->possible_crtcs = BIT(0);
 166
 167        ret = drm_bridge_attach(priv->external_encoder, bridge, NULL);
 168        if (ret) {
 169                dev_err(ddev->dev, "drm_bridge_attach() failed %d\n", ret);
 170                return ret;
 171        }
 172
 173        tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_default);
 174
 175        connector = tilcdc_encoder_find_connector(ddev, priv->external_encoder);
 176        if (!connector)
 177                return -ENODEV;
 178
 179        ret = tilcdc_add_external_connector(ddev, connector);
 180
 181        return ret;
 182}
 183
 184int tilcdc_attach_external_device(struct drm_device *ddev)
 185{
 186        struct tilcdc_drm_private *priv = ddev->dev_private;
 187        struct drm_bridge *bridge;
 188        struct drm_panel *panel;
 189        int ret;
 190
 191        ret = drm_of_find_panel_or_bridge(ddev->dev->of_node, 0, 0,
 192                                          &panel, &bridge);
 193        if (ret == -ENODEV)
 194                return 0;
 195        else if (ret)
 196                return ret;
 197
 198        priv->external_encoder = devm_kzalloc(ddev->dev,
 199                                              sizeof(*priv->external_encoder),
 200                                              GFP_KERNEL);
 201        if (!priv->external_encoder)
 202                return -ENOMEM;
 203
 204        ret = drm_encoder_init(ddev, priv->external_encoder,
 205                               &tilcdc_external_encoder_funcs,
 206                               DRM_MODE_ENCODER_NONE, NULL);
 207        if (ret) {
 208                dev_err(ddev->dev, "drm_encoder_init() failed %d\n", ret);
 209                return ret;
 210        }
 211
 212        if (panel) {
 213                bridge = devm_drm_panel_bridge_add(ddev->dev, panel,
 214                                                   DRM_MODE_CONNECTOR_DPI);
 215                if (IS_ERR(bridge)) {
 216                        ret = PTR_ERR(bridge);
 217                        goto err_encoder_cleanup;
 218                }
 219        }
 220
 221        ret = tilcdc_attach_bridge(ddev, bridge);
 222        if (ret)
 223                goto err_encoder_cleanup;
 224
 225        return 0;
 226
 227err_encoder_cleanup:
 228        drm_encoder_cleanup(priv->external_encoder);
 229        return ret;
 230}
 231
 232static int dev_match_of(struct device *dev, void *data)
 233{
 234        return dev->of_node == data;
 235}
 236
 237int tilcdc_get_external_components(struct device *dev,
 238                                   struct component_match **match)
 239{
 240        struct device_node *node;
 241
 242        node = of_graph_get_remote_node(dev->of_node, 0, 0);
 243
 244        if (!of_device_is_compatible(node, "nxp,tda998x")) {
 245                of_node_put(node);
 246                return 0;
 247        }
 248
 249        if (match)
 250                drm_of_component_match_add(dev, match, dev_match_of, node);
 251        of_node_put(node);
 252        return 1;
 253}
 254