linux/drivers/gpu/drm/sun4i/sun4i_drv.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2015 Free Electrons
   4 * Copyright (C) 2015 NextThing Co
   5 *
   6 * Maxime Ripard <maxime.ripard@free-electrons.com>
   7 */
   8
   9#include <linux/component.h>
  10#include <linux/kfifo.h>
  11#include <linux/module.h>
  12#include <linux/of_graph.h>
  13#include <linux/of_reserved_mem.h>
  14#include <linux/platform_device.h>
  15
  16#include <drm/drm_aperture.h>
  17#include <drm/drm_atomic_helper.h>
  18#include <drm/drm_drv.h>
  19#include <drm/drm_fb_cma_helper.h>
  20#include <drm/drm_fb_helper.h>
  21#include <drm/drm_gem_cma_helper.h>
  22#include <drm/drm_of.h>
  23#include <drm/drm_probe_helper.h>
  24#include <drm/drm_vblank.h>
  25
  26#include "sun4i_drv.h"
  27#include "sun4i_frontend.h"
  28#include "sun4i_framebuffer.h"
  29#include "sun4i_tcon.h"
  30#include "sun8i_tcon_top.h"
  31
  32static int drm_sun4i_gem_dumb_create(struct drm_file *file_priv,
  33                                     struct drm_device *drm,
  34                                     struct drm_mode_create_dumb *args)
  35{
  36        /* The hardware only allows even pitches for YUV buffers. */
  37        args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 2);
  38
  39        return drm_gem_cma_dumb_create_internal(file_priv, drm, args);
  40}
  41
  42DEFINE_DRM_GEM_CMA_FOPS(sun4i_drv_fops);
  43
  44static const struct drm_driver sun4i_drv_driver = {
  45        .driver_features        = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
  46
  47        /* Generic Operations */
  48        .fops                   = &sun4i_drv_fops,
  49        .name                   = "sun4i-drm",
  50        .desc                   = "Allwinner sun4i Display Engine",
  51        .date                   = "20150629",
  52        .major                  = 1,
  53        .minor                  = 0,
  54
  55        /* GEM Operations */
  56        DRM_GEM_CMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(drm_sun4i_gem_dumb_create),
  57};
  58
  59static int sun4i_drv_bind(struct device *dev)
  60{
  61        struct drm_device *drm;
  62        struct sun4i_drv *drv;
  63        int ret;
  64
  65        drm = drm_dev_alloc(&sun4i_drv_driver, dev);
  66        if (IS_ERR(drm))
  67                return PTR_ERR(drm);
  68
  69        drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
  70        if (!drv) {
  71                ret = -ENOMEM;
  72                goto free_drm;
  73        }
  74
  75        dev_set_drvdata(dev, drm);
  76        drm->dev_private = drv;
  77        INIT_LIST_HEAD(&drv->frontend_list);
  78        INIT_LIST_HEAD(&drv->engine_list);
  79        INIT_LIST_HEAD(&drv->tcon_list);
  80
  81        ret = of_reserved_mem_device_init(dev);
  82        if (ret && ret != -ENODEV) {
  83                dev_err(drm->dev, "Couldn't claim our memory region\n");
  84                goto free_drm;
  85        }
  86
  87        drm_mode_config_init(drm);
  88
  89        ret = component_bind_all(drm->dev, drm);
  90        if (ret) {
  91                dev_err(drm->dev, "Couldn't bind all pipelines components\n");
  92                goto cleanup_mode_config;
  93        }
  94
  95        /* drm_vblank_init calls kcalloc, which can fail */
  96        ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
  97        if (ret)
  98                goto cleanup_mode_config;
  99
 100        /* Remove early framebuffers (ie. simplefb) */
 101        ret = drm_aperture_remove_framebuffers(false, &sun4i_drv_driver);
 102        if (ret)
 103                goto cleanup_mode_config;
 104
 105        sun4i_framebuffer_init(drm);
 106
 107        /* Enable connectors polling */
 108        drm_kms_helper_poll_init(drm);
 109
 110        ret = drm_dev_register(drm, 0);
 111        if (ret)
 112                goto finish_poll;
 113
 114        drm_fbdev_generic_setup(drm, 32);
 115
 116        return 0;
 117
 118finish_poll:
 119        drm_kms_helper_poll_fini(drm);
 120cleanup_mode_config:
 121        drm_mode_config_cleanup(drm);
 122        of_reserved_mem_device_release(dev);
 123free_drm:
 124        drm_dev_put(drm);
 125        return ret;
 126}
 127
 128static void sun4i_drv_unbind(struct device *dev)
 129{
 130        struct drm_device *drm = dev_get_drvdata(dev);
 131
 132        drm_dev_unregister(drm);
 133        drm_kms_helper_poll_fini(drm);
 134        drm_atomic_helper_shutdown(drm);
 135        drm_mode_config_cleanup(drm);
 136
 137        component_unbind_all(dev, NULL);
 138        of_reserved_mem_device_release(dev);
 139
 140        drm_dev_put(drm);
 141}
 142
 143static const struct component_master_ops sun4i_drv_master_ops = {
 144        .bind   = sun4i_drv_bind,
 145        .unbind = sun4i_drv_unbind,
 146};
 147
 148static bool sun4i_drv_node_is_connector(struct device_node *node)
 149{
 150        return of_device_is_compatible(node, "hdmi-connector");
 151}
 152
 153static bool sun4i_drv_node_is_frontend(struct device_node *node)
 154{
 155        return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") ||
 156                of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
 157                of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") ||
 158                of_device_is_compatible(node, "allwinner,sun7i-a20-display-frontend") ||
 159                of_device_is_compatible(node, "allwinner,sun8i-a23-display-frontend") ||
 160                of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend") ||
 161                of_device_is_compatible(node, "allwinner,sun9i-a80-display-frontend");
 162}
 163
 164static bool sun4i_drv_node_is_deu(struct device_node *node)
 165{
 166        return of_device_is_compatible(node, "allwinner,sun9i-a80-deu");
 167}
 168
 169static bool sun4i_drv_node_is_supported_frontend(struct device_node *node)
 170{
 171        if (IS_ENABLED(CONFIG_DRM_SUN4I_BACKEND))
 172                return !!of_match_node(sun4i_frontend_of_table, node);
 173
 174        return false;
 175}
 176
 177static bool sun4i_drv_node_is_tcon(struct device_node *node)
 178{
 179        return !!of_match_node(sun4i_tcon_of_table, node);
 180}
 181
 182static bool sun4i_drv_node_is_tcon_with_ch0(struct device_node *node)
 183{
 184        const struct of_device_id *match;
 185
 186        match = of_match_node(sun4i_tcon_of_table, node);
 187        if (match) {
 188                struct sun4i_tcon_quirks *quirks;
 189
 190                quirks = (struct sun4i_tcon_quirks *)match->data;
 191
 192                return quirks->has_channel_0;
 193        }
 194
 195        return false;
 196}
 197
 198static bool sun4i_drv_node_is_tcon_top(struct device_node *node)
 199{
 200        return IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) &&
 201                !!of_match_node(sun8i_tcon_top_of_table, node);
 202}
 203
 204static int compare_of(struct device *dev, void *data)
 205{
 206        DRM_DEBUG_DRIVER("Comparing of node %pOF with %pOF\n",
 207                         dev->of_node,
 208                         data);
 209
 210        return dev->of_node == data;
 211}
 212
 213/*
 214 * The encoder drivers use drm_of_find_possible_crtcs to get upstream
 215 * crtcs from the device tree using of_graph. For the results to be
 216 * correct, encoders must be probed/bound after _all_ crtcs have been
 217 * created. The existing code uses a depth first recursive traversal
 218 * of the of_graph, which means the encoders downstream of the TCON
 219 * get add right after the first TCON. The second TCON or CRTC will
 220 * never be properly associated with encoders connected to it.
 221 *
 222 * Also, in a dual display pipeline setup, both frontends can feed
 223 * either backend, and both backends can feed either TCON, we want
 224 * all components of the same type to be added before the next type
 225 * in the pipeline. Fortunately, the pipelines are perfectly symmetric,
 226 * i.e. components of the same type are at the same depth when counted
 227 * from the frontend. The only exception is the third pipeline in
 228 * the A80 SoC, which we do not support anyway.
 229 *
 230 * Hence we can use a breadth first search traversal order to add
 231 * components. We do not need to check for duplicates. The component
 232 * matching system handles this for us.
 233 */
 234struct endpoint_list {
 235        DECLARE_KFIFO(fifo, struct device_node *, 16);
 236};
 237
 238static void sun4i_drv_traverse_endpoints(struct endpoint_list *list,
 239                                         struct device_node *node,
 240                                         int port_id)
 241{
 242        struct device_node *ep, *remote, *port;
 243
 244        port = of_graph_get_port_by_id(node, port_id);
 245        if (!port) {
 246                DRM_DEBUG_DRIVER("No output to bind on port %d\n", port_id);
 247                return;
 248        }
 249
 250        for_each_available_child_of_node(port, ep) {
 251                remote = of_graph_get_remote_port_parent(ep);
 252                if (!remote) {
 253                        DRM_DEBUG_DRIVER("Error retrieving the output node\n");
 254                        continue;
 255                }
 256
 257                if (sun4i_drv_node_is_tcon(node)) {
 258                        /*
 259                         * TCON TOP is always probed before TCON. However, TCON
 260                         * points back to TCON TOP when it is source for HDMI.
 261                         * We have to skip it here to prevent infinite looping
 262                         * between TCON TOP and TCON.
 263                         */
 264                        if (sun4i_drv_node_is_tcon_top(remote)) {
 265                                DRM_DEBUG_DRIVER("TCON output endpoint is TCON TOP... skipping\n");
 266                                of_node_put(remote);
 267                                continue;
 268                        }
 269
 270                        /*
 271                         * If the node is our TCON with channel 0, the first
 272                         * port is used for panel or bridges, and will not be
 273                         * part of the component framework.
 274                         */
 275                        if (sun4i_drv_node_is_tcon_with_ch0(node)) {
 276                                struct of_endpoint endpoint;
 277
 278                                if (of_graph_parse_endpoint(ep, &endpoint)) {
 279                                        DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
 280                                        of_node_put(remote);
 281                                        continue;
 282                                }
 283
 284                                if (!endpoint.id) {
 285                                        DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
 286                                        of_node_put(remote);
 287                                        continue;
 288                                }
 289                        }
 290                }
 291
 292                kfifo_put(&list->fifo, remote);
 293        }
 294}
 295
 296static int sun4i_drv_add_endpoints(struct device *dev,
 297                                   struct endpoint_list *list,
 298                                   struct component_match **match,
 299                                   struct device_node *node)
 300{
 301        int count = 0;
 302
 303        /*
 304         * The frontend has been disabled in some of our old device
 305         * trees. If we find a node that is the frontend and is
 306         * disabled, we should just follow through and parse its
 307         * child, but without adding it to the component list.
 308         * Otherwise, we obviously want to add it to the list.
 309         */
 310        if (!sun4i_drv_node_is_frontend(node) &&
 311            !of_device_is_available(node))
 312                return 0;
 313
 314        /*
 315         * The connectors will be the last nodes in our pipeline, we
 316         * can just bail out.
 317         */
 318        if (sun4i_drv_node_is_connector(node))
 319                return 0;
 320
 321        /*
 322         * If the device is either just a regular device, or an
 323         * enabled frontend supported by the driver, we add it to our
 324         * component list.
 325         */
 326        if (!(sun4i_drv_node_is_frontend(node) ||
 327              sun4i_drv_node_is_deu(node)) ||
 328            (sun4i_drv_node_is_supported_frontend(node) &&
 329             of_device_is_available(node))) {
 330                /* Add current component */
 331                DRM_DEBUG_DRIVER("Adding component %pOF\n", node);
 332                drm_of_component_match_add(dev, match, compare_of, node);
 333                count++;
 334        }
 335
 336        /* each node has at least one output */
 337        sun4i_drv_traverse_endpoints(list, node, 1);
 338
 339        /* TCON TOP has second and third output */
 340        if (sun4i_drv_node_is_tcon_top(node)) {
 341                sun4i_drv_traverse_endpoints(list, node, 3);
 342                sun4i_drv_traverse_endpoints(list, node, 5);
 343        }
 344
 345        return count;
 346}
 347
 348#ifdef CONFIG_PM_SLEEP
 349static int sun4i_drv_drm_sys_suspend(struct device *dev)
 350{
 351        struct drm_device *drm = dev_get_drvdata(dev);
 352
 353        return drm_mode_config_helper_suspend(drm);
 354}
 355
 356static int sun4i_drv_drm_sys_resume(struct device *dev)
 357{
 358        struct drm_device *drm = dev_get_drvdata(dev);
 359
 360        return drm_mode_config_helper_resume(drm);
 361}
 362#endif
 363
 364static const struct dev_pm_ops sun4i_drv_drm_pm_ops = {
 365        SET_SYSTEM_SLEEP_PM_OPS(sun4i_drv_drm_sys_suspend,
 366                                sun4i_drv_drm_sys_resume)
 367};
 368
 369static int sun4i_drv_probe(struct platform_device *pdev)
 370{
 371        struct component_match *match = NULL;
 372        struct device_node *np = pdev->dev.of_node, *endpoint;
 373        struct endpoint_list list;
 374        int i, ret, count = 0;
 375
 376        INIT_KFIFO(list.fifo);
 377
 378        for (i = 0;; i++) {
 379                struct device_node *pipeline = of_parse_phandle(np,
 380                                                                "allwinner,pipelines",
 381                                                                i);
 382                if (!pipeline)
 383                        break;
 384
 385                kfifo_put(&list.fifo, pipeline);
 386        }
 387
 388        while (kfifo_get(&list.fifo, &endpoint)) {
 389                /* process this endpoint */
 390                ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match,
 391                                              endpoint);
 392
 393                /* sun4i_drv_add_endpoints can fail to allocate memory */
 394                if (ret < 0)
 395                        return ret;
 396
 397                count += ret;
 398        }
 399
 400        if (count)
 401                return component_master_add_with_match(&pdev->dev,
 402                                                       &sun4i_drv_master_ops,
 403                                                       match);
 404        else
 405                return 0;
 406}
 407
 408static int sun4i_drv_remove(struct platform_device *pdev)
 409{
 410        component_master_del(&pdev->dev, &sun4i_drv_master_ops);
 411
 412        return 0;
 413}
 414
 415static const struct of_device_id sun4i_drv_of_table[] = {
 416        { .compatible = "allwinner,sun4i-a10-display-engine" },
 417        { .compatible = "allwinner,sun5i-a10s-display-engine" },
 418        { .compatible = "allwinner,sun5i-a13-display-engine" },
 419        { .compatible = "allwinner,sun6i-a31-display-engine" },
 420        { .compatible = "allwinner,sun6i-a31s-display-engine" },
 421        { .compatible = "allwinner,sun7i-a20-display-engine" },
 422        { .compatible = "allwinner,sun8i-a23-display-engine" },
 423        { .compatible = "allwinner,sun8i-a33-display-engine" },
 424        { .compatible = "allwinner,sun8i-a83t-display-engine" },
 425        { .compatible = "allwinner,sun8i-h3-display-engine" },
 426        { .compatible = "allwinner,sun8i-r40-display-engine" },
 427        { .compatible = "allwinner,sun8i-v3s-display-engine" },
 428        { .compatible = "allwinner,sun9i-a80-display-engine" },
 429        { .compatible = "allwinner,sun50i-a64-display-engine" },
 430        { .compatible = "allwinner,sun50i-h6-display-engine" },
 431        { }
 432};
 433MODULE_DEVICE_TABLE(of, sun4i_drv_of_table);
 434
 435static struct platform_driver sun4i_drv_platform_driver = {
 436        .probe          = sun4i_drv_probe,
 437        .remove         = sun4i_drv_remove,
 438        .driver         = {
 439                .name           = "sun4i-drm",
 440                .of_match_table = sun4i_drv_of_table,
 441                .pm = &sun4i_drv_drm_pm_ops,
 442        },
 443};
 444module_platform_driver(sun4i_drv_platform_driver);
 445
 446MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
 447MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
 448MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver");
 449MODULE_LICENSE("GPL");
 450