diff options
Diffstat (limited to 'drivers/gpu/drm/sun4i/sun4i_drv.c')
| -rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_drv.c | 396 |
1 files changed, 263 insertions, 133 deletions
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index abc7d8fe06b4..8a409eee1dca 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -1,80 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2015 Free Electrons * Copyright (C) 2015 NextThing Co * * Maxime Ripard <maxime.ripard@free-electrons.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. */ +#include <linux/aperture.h> #include <linux/component.h> +#include <linux/dma-mapping.h> +#include <linux/kfifo.h> +#include <linux/module.h> #include <linux/of_graph.h> #include <linux/of_reserved_mem.h> - -#include <drm/drmP.h> -#include <drm/drm_crtc_helper.h> -#include <drm/drm_fb_cma_helper.h> -#include <drm/drm_gem_cma_helper.h> -#include <drm/drm_fb_helper.h> +#include <linux/platform_device.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_module.h> #include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> #include "sun4i_drv.h" +#include "sun4i_frontend.h" #include "sun4i_framebuffer.h" #include "sun4i_tcon.h" +#include "sun8i_tcon_top.h" -DEFINE_DRM_GEM_CMA_FOPS(sun4i_drv_fops); +static int drm_sun4i_gem_dumb_create(struct drm_file *file_priv, + struct drm_device *drm, + struct drm_mode_create_dumb *args) +{ + /* The hardware only allows even pitches for YUV buffers. */ + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 2); -static struct drm_driver sun4i_drv_driver = { - .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, + return drm_gem_dma_dumb_create_internal(file_priv, drm, args); +} + +DEFINE_DRM_GEM_DMA_FOPS(sun4i_drv_fops); + +static const struct drm_driver sun4i_drv_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, /* Generic Operations */ .fops = &sun4i_drv_fops, .name = "sun4i-drm", .desc = "Allwinner sun4i Display Engine", - .date = "20150629", .major = 1, .minor = 0, /* GEM Operations */ - .dumb_create = drm_gem_cma_dumb_create, - .dumb_destroy = drm_gem_dumb_destroy, - .dumb_map_offset = drm_gem_cma_dumb_map_offset, - .gem_free_object_unlocked = drm_gem_cma_free_object, - .gem_vm_ops = &drm_gem_cma_vm_ops, - - /* PRIME Operations */ - .prime_handle_to_fd = drm_gem_prime_handle_to_fd, - .prime_fd_to_handle = drm_gem_prime_fd_to_handle, - .gem_prime_import = drm_gem_prime_import, - .gem_prime_export = drm_gem_prime_export, - .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, - .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, - .gem_prime_vmap = drm_gem_cma_prime_vmap, - .gem_prime_vunmap = drm_gem_cma_prime_vunmap, - .gem_prime_mmap = drm_gem_cma_prime_mmap, - - /* Frame Buffer Operations */ + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(drm_sun4i_gem_dumb_create), + DRM_FBDEV_DMA_DRIVER_OPS, }; -static void sun4i_remove_framebuffers(void) -{ - struct apertures_struct *ap; - - ap = alloc_apertures(1); - if (!ap) - return; - - /* The framebuffer can be located anywhere in RAM */ - ap->ranges[0].base = 0; - ap->ranges[0].size = ~0; - - drm_fb_helper_remove_conflicting_framebuffers(ap, "sun4i-drm-fb", false); - kfree(ap); -} - static int sun4i_drv_bind(struct device *dev) { struct drm_device *drm; @@ -90,7 +74,9 @@ static int sun4i_drv_bind(struct device *dev) ret = -ENOMEM; goto free_drm; } + drm->dev_private = drv; + INIT_LIST_HEAD(&drv->frontend_list); INIT_LIST_HEAD(&drv->engine_list); INIT_LIST_HEAD(&drv->tcon_list); @@ -100,11 +86,6 @@ static int sun4i_drv_bind(struct device *dev) goto free_drm; } - /* drm_vblank_init calls kcalloc, which can fail */ - ret = drm_vblank_init(drm, 1); - if (ret) - goto free_mem_region; - drm_mode_config_init(drm); ret = component_bind_all(drm->dev, drm); @@ -113,18 +94,17 @@ static int sun4i_drv_bind(struct device *dev) goto cleanup_mode_config; } - drm->irq_enabled = true; + /* drm_vblank_init calls kcalloc, which can fail */ + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + if (ret) + goto unbind_all; /* Remove early framebuffers (ie. simplefb) */ - sun4i_remove_framebuffers(); + ret = aperture_remove_all_conflicting_devices(sun4i_drv_driver.name); + if (ret) + goto unbind_all; - /* Create our framebuffer */ - drv->fbdev = sun4i_framebuffer_init(drm); - if (IS_ERR(drv->fbdev)) { - dev_err(drm->dev, "Couldn't create our framebuffer\n"); - ret = PTR_ERR(drv->fbdev); - goto cleanup_mode_config; - } + sun4i_framebuffer_init(drm); /* Enable connectors polling */ drm_kms_helper_poll_init(drm); @@ -133,17 +113,21 @@ static int sun4i_drv_bind(struct device *dev) if (ret) goto finish_poll; + drm_client_setup(drm, NULL); + + dev_set_drvdata(dev, drm); + return 0; finish_poll: drm_kms_helper_poll_fini(drm); - sun4i_framebuffer_free(drm); +unbind_all: + component_unbind_all(dev, NULL); cleanup_mode_config: drm_mode_config_cleanup(drm); -free_mem_region: of_reserved_mem_device_release(dev); free_drm: - drm_dev_unref(drm); + drm_dev_put(drm); return ret; } @@ -151,12 +135,16 @@ static void sun4i_drv_unbind(struct device *dev) { struct drm_device *drm = dev_get_drvdata(dev); + dev_set_drvdata(dev, NULL); drm_dev_unregister(drm); drm_kms_helper_poll_fini(drm); - sun4i_framebuffer_free(drm); + drm_atomic_helper_shutdown(drm); drm_mode_config_cleanup(drm); + + component_unbind_all(dev, NULL); of_reserved_mem_device_release(dev); - drm_dev_unref(drm); + + drm_dev_put(drm); } static const struct component_master_ops sun4i_drv_master_ops = { @@ -171,40 +159,151 @@ static bool sun4i_drv_node_is_connector(struct device_node *node) static bool sun4i_drv_node_is_frontend(struct device_node *node) { - return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || + return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") || + of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") || - of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend"); + of_device_is_compatible(node, "allwinner,sun7i-a20-display-frontend") || + of_device_is_compatible(node, "allwinner,sun8i-a23-display-frontend") || + of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend") || + of_device_is_compatible(node, "allwinner,sun9i-a80-display-frontend"); +} + +static bool sun4i_drv_node_is_deu(struct device_node *node) +{ + return of_device_is_compatible(node, "allwinner,sun9i-a80-deu"); +} + +static bool sun4i_drv_node_is_supported_frontend(struct device_node *node) +{ + if (IS_ENABLED(CONFIG_DRM_SUN4I_BACKEND)) + return !!of_match_node(sun4i_frontend_of_table, node); + + return false; } static bool sun4i_drv_node_is_tcon(struct device_node *node) { - return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || - of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") || - of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") || - of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") || - of_device_is_compatible(node, "allwinner,sun8i-v3s-tcon"); + return !!of_match_node(sun4i_tcon_of_table, node); } -static int compare_of(struct device *dev, void *data) +static bool sun4i_drv_node_is_tcon_with_ch0(struct device_node *node) { - DRM_DEBUG_DRIVER("Comparing of node %s with %s\n", - of_node_full_name(dev->of_node), - of_node_full_name(data)); + const struct of_device_id *match; + + match = of_match_node(sun4i_tcon_of_table, node); + if (match) { + struct sun4i_tcon_quirks *quirks; + + quirks = (struct sun4i_tcon_quirks *)match->data; - return dev->of_node == data; + return quirks->has_channel_0; + } + + return false; +} + +static bool sun4i_drv_node_is_tcon_top(struct device_node *node) +{ + return IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && + !!of_match_node(sun8i_tcon_top_of_table, node); +} + +/* + * The encoder drivers use drm_of_find_possible_crtcs to get upstream + * crtcs from the device tree using of_graph. For the results to be + * correct, encoders must be probed/bound after _all_ crtcs have been + * created. The existing code uses a depth first recursive traversal + * of the of_graph, which means the encoders downstream of the TCON + * get add right after the first TCON. The second TCON or CRTC will + * never be properly associated with encoders connected to it. + * + * Also, in a dual display pipeline setup, both frontends can feed + * either backend, and both backends can feed either TCON, we want + * all components of the same type to be added before the next type + * in the pipeline. Fortunately, the pipelines are perfectly symmetric, + * i.e. components of the same type are at the same depth when counted + * from the frontend. The only exception is the third pipeline in + * the A80 SoC, which we do not support anyway. + * + * Hence we can use a breadth first search traversal order to add + * components. We do not need to check for duplicates. The component + * matching system handles this for us. + */ +struct endpoint_list { + DECLARE_KFIFO(fifo, struct device_node *, 16); +}; + +static void sun4i_drv_traverse_endpoints(struct endpoint_list *list, + struct device_node *node, + int port_id) +{ + struct device_node *ep, *remote, *port; + + port = of_graph_get_port_by_id(node, port_id); + if (!port) { + DRM_DEBUG_DRIVER("No output to bind on port %d\n", port_id); + return; + } + + for_each_available_child_of_node(port, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + DRM_DEBUG_DRIVER("Error retrieving the output node\n"); + continue; + } + + if (sun4i_drv_node_is_tcon(node)) { + /* + * TCON TOP is always probed before TCON. However, TCON + * points back to TCON TOP when it is source for HDMI. + * We have to skip it here to prevent infinite looping + * between TCON TOP and TCON. + */ + if (sun4i_drv_node_is_tcon_top(remote)) { + DRM_DEBUG_DRIVER("TCON output endpoint is TCON TOP... skipping\n"); + of_node_put(remote); + continue; + } + + /* + * If the node is our TCON with channel 0, the first + * port is used for panel or bridges, and will not be + * part of the component framework. + */ + if (sun4i_drv_node_is_tcon_with_ch0(node)) { + struct of_endpoint endpoint; + + if (of_graph_parse_endpoint(ep, &endpoint)) { + DRM_DEBUG_DRIVER("Couldn't parse endpoint\n"); + of_node_put(remote); + continue; + } + + if (!endpoint.id) { + DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n"); + of_node_put(remote); + continue; + } + } + } + + kfifo_put(&list->fifo, remote); + } } static int sun4i_drv_add_endpoints(struct device *dev, + struct endpoint_list *list, struct component_match **match, struct device_node *node) { - struct device_node *port, *ep, *remote; int count = 0; /* - * We don't support the frontend for now, so we will never - * have a device bound. Just skip over it, but we still want - * the rest our pipeline to be added. + * The frontend has been disabled in some of our old device + * trees. If we find a node that is the frontend and is + * disabled, we should just follow through and parse its + * child, but without adding it to the component list. + * Otherwise, we obviously want to add it to the list. */ if (!sun4i_drv_node_is_frontend(node) && !of_device_is_available(node)) @@ -217,62 +316,69 @@ static int sun4i_drv_add_endpoints(struct device *dev, if (sun4i_drv_node_is_connector(node)) return 0; - if (!sun4i_drv_node_is_frontend(node)) { + /* + * If the device is either just a regular device, or an + * enabled frontend supported by the driver, we add it to our + * component list. + */ + if (!(sun4i_drv_node_is_frontend(node) || + sun4i_drv_node_is_deu(node)) || + (sun4i_drv_node_is_supported_frontend(node) && + of_device_is_available(node))) { /* Add current component */ - DRM_DEBUG_DRIVER("Adding component %s\n", - of_node_full_name(node)); - drm_of_component_match_add(dev, match, compare_of, node); + DRM_DEBUG_DRIVER("Adding component %pOF\n", node); + drm_of_component_match_add(dev, match, component_compare_of, node); count++; } - /* Inputs are listed first, then outputs */ - port = of_graph_get_port_by_id(node, 1); - if (!port) { - DRM_DEBUG_DRIVER("No output to bind\n"); - return count; - } + /* each node has at least one output */ + sun4i_drv_traverse_endpoints(list, node, 1); - for_each_available_child_of_node(port, ep) { - remote = of_graph_get_remote_port_parent(ep); - if (!remote) { - DRM_DEBUG_DRIVER("Error retrieving the output node\n"); - of_node_put(remote); - continue; - } - - /* - * If the node is our TCON, the first port is used for - * panel or bridges, and will not be part of the - * component framework. - */ - if (sun4i_drv_node_is_tcon(node)) { - struct of_endpoint endpoint; + /* TCON TOP has second and third output */ + if (sun4i_drv_node_is_tcon_top(node)) { + sun4i_drv_traverse_endpoints(list, node, 3); + sun4i_drv_traverse_endpoints(list, node, 5); + } - if (of_graph_parse_endpoint(ep, &endpoint)) { - DRM_DEBUG_DRIVER("Couldn't parse endpoint\n"); - continue; - } + return count; +} - if (!endpoint.id) { - DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n"); - continue; - } - } +#ifdef CONFIG_PM_SLEEP +static int sun4i_drv_drm_sys_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); - /* Walk down our tree */ - count += sun4i_drv_add_endpoints(dev, match, remote); + return drm_mode_config_helper_suspend(drm); +} - of_node_put(remote); - } +static int sun4i_drv_drm_sys_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); - return count; + return drm_mode_config_helper_resume(drm); } +#endif + +static const struct dev_pm_ops sun4i_drv_drm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sun4i_drv_drm_sys_suspend, + sun4i_drv_drm_sys_resume) +}; static int sun4i_drv_probe(struct platform_device *pdev) { struct component_match *match = NULL; - struct device_node *np = pdev->dev.of_node; - int i, count = 0; + struct device_node *np = pdev->dev.of_node, *endpoint; + struct endpoint_list list; + int i, ret, count = 0; + + INIT_KFIFO(list.fifo); + + /* + * DE2 and DE3 cores actually supports 40-bit addresses, but + * driver does not. + */ + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + dma_set_max_seg_size(&pdev->dev, UINT_MAX); for (i = 0;; i++) { struct device_node *pipeline = of_parse_phandle(np, @@ -281,12 +387,19 @@ static int sun4i_drv_probe(struct platform_device *pdev) if (!pipeline) break; - count += sun4i_drv_add_endpoints(&pdev->dev, &match, - pipeline); - of_node_put(pipeline); + kfifo_put(&list.fifo, pipeline); + } - DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n", - count, i); + while (kfifo_get(&list.fifo, &endpoint)) { + /* process this endpoint */ + ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match, + endpoint); + + /* sun4i_drv_add_endpoints can fail to allocate memory */ + if (ret < 0) + return ret; + + count += ret; } if (count) @@ -297,18 +410,33 @@ static int sun4i_drv_probe(struct platform_device *pdev) return 0; } -static int sun4i_drv_remove(struct platform_device *pdev) +static void sun4i_drv_remove(struct platform_device *pdev) { - return 0; + component_master_del(&pdev->dev, &sun4i_drv_master_ops); +} + +static void sun4i_drv_shutdown(struct platform_device *pdev) +{ + drm_atomic_helper_shutdown(platform_get_drvdata(pdev)); } static const struct of_device_id sun4i_drv_of_table[] = { + { .compatible = "allwinner,sun4i-a10-display-engine" }, { .compatible = "allwinner,sun5i-a10s-display-engine" }, { .compatible = "allwinner,sun5i-a13-display-engine" }, { .compatible = "allwinner,sun6i-a31-display-engine" }, { .compatible = "allwinner,sun6i-a31s-display-engine" }, + { .compatible = "allwinner,sun7i-a20-display-engine" }, + { .compatible = "allwinner,sun8i-a23-display-engine" }, { .compatible = "allwinner,sun8i-a33-display-engine" }, + { .compatible = "allwinner,sun8i-a83t-display-engine" }, + { .compatible = "allwinner,sun8i-h3-display-engine" }, + { .compatible = "allwinner,sun8i-r40-display-engine" }, { .compatible = "allwinner,sun8i-v3s-display-engine" }, + { .compatible = "allwinner,sun9i-a80-display-engine" }, + { .compatible = "allwinner,sun20i-d1-display-engine" }, + { .compatible = "allwinner,sun50i-a64-display-engine" }, + { .compatible = "allwinner,sun50i-h6-display-engine" }, { } }; MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); @@ -316,12 +444,14 @@ MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); static struct platform_driver sun4i_drv_platform_driver = { .probe = sun4i_drv_probe, .remove = sun4i_drv_remove, + .shutdown = sun4i_drv_shutdown, .driver = { .name = "sun4i-drm", .of_match_table = sun4i_drv_of_table, + .pm = &sun4i_drv_drm_pm_ops, }, }; -module_platform_driver(sun4i_drv_platform_driver); +drm_module_platform_driver(sun4i_drv_platform_driver); MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); |
