diff options
Diffstat (limited to 'drivers/gpu/drm/meson/meson_drv.c')
| -rw-r--r-- | drivers/gpu/drm/meson/meson_drv.c | 482 |
1 files changed, 327 insertions, 155 deletions
diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c index 4d98fac92795..49ff9f1f16d3 100644 --- a/drivers/gpu/drm/meson/meson_drv.c +++ b/drivers/gpu/drm/meson/meson_drv.c @@ -1,53 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2016 BayLibre, SAS * Author: Neil Armstrong <narmstrong@baylibre.com> * Copyright (C) 2014 Endless Mobile * - * 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. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see <http://www.gnu.org/licenses/>. - * * Written by: * Jasper St. Pierre <jstpierre@mecheye.net> */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/platform_device.h> +#include <linux/aperture.h> #include <linux/component.h> +#include <linux/module.h> #include <linux/of_graph.h> +#include <linux/sys_soc.h> +#include <linux/platform_device.h> +#include <linux/soc/amlogic/meson-canvas.h> -#include <drm/drmP.h> -#include <drm/drm_atomic.h> +#include <drm/clients/drm_client_setup.h> #include <drm/drm_atomic_helper.h> -#include <drm/drm_flip_work.h> -#include <drm/drm_crtc_helper.h> -#include <drm/drm_plane_helper.h> -#include <drm/drm_gem_cma_helper.h> -#include <drm/drm_fb_cma_helper.h> -#include <drm/drm_rect.h> -#include <drm/drm_fb_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_module.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> +#include "meson_crtc.h" #include "meson_drv.h" +#include "meson_overlay.h" #include "meson_plane.h" -#include "meson_crtc.h" -#include "meson_venc_cvbs.h" - -#include "meson_vpp.h" -#include "meson_viu.h" -#include "meson_venc.h" -#include "meson_canvas.h" +#include "meson_osd_afbcd.h" #include "meson_registers.h" +#include "meson_encoder_cvbs.h" +#include "meson_encoder_hdmi.h" +#include "meson_encoder_dsi.h" +#include "meson_viu.h" +#include "meson_vpp.h" +#include "meson_rdma.h" #define DRIVER_NAME "meson" #define DRIVER_DESC "Amlogic Meson DRM driver" @@ -67,18 +58,14 @@ * - Powering Up HDMI controller and PHY */ -static void meson_fb_output_poll_changed(struct drm_device *dev) -{ - struct meson_drm *priv = dev->dev_private; - - drm_fbdev_cma_hotplug_event(priv->fbdev); -} - static const struct drm_mode_config_funcs meson_mode_config_funcs = { - .output_poll_changed = meson_fb_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, - .fb_create = drm_fb_cma_create, + .fb_create = drm_gem_fb_create, +}; + +static const struct drm_mode_config_helper_funcs meson_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, }; static irqreturn_t meson_irq(int irq, void *arg) @@ -93,39 +80,31 @@ static irqreturn_t meson_irq(int irq, void *arg) return IRQ_HANDLED; } -DEFINE_DRM_GEM_CMA_FOPS(fops); - -static struct drm_driver meson_driver = { - .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | - DRIVER_MODESET | DRIVER_PRIME | - DRIVER_ATOMIC, - - /* IRQ */ - .irq_handler = meson_irq, - - /* PRIME Ops */ - .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, - - /* GEM Ops */ - .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, +static int meson_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + /* + * We need 64bytes aligned stride, and PAGE aligned size + */ + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), SZ_64); + args->size = PAGE_ALIGN(args->pitch * args->height); + + return drm_gem_dma_dumb_create_internal(file, dev, args); +} + +DEFINE_DRM_GEM_DMA_FOPS(fops); + +static const struct drm_driver meson_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + + /* DMA Ops */ + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(meson_dumb_create), + DRM_FBDEV_DMA_DRIVER_OPS, /* Misc */ .fops = &fops, .name = DRIVER_NAME, .desc = DRIVER_DESC, - .date = "20161109", .major = 1, .minor = 0, }; @@ -138,28 +117,76 @@ static bool meson_vpu_has_available_connectors(struct device *dev) for_each_endpoint_of_node(dev->of_node, ep) { /* If the endpoint node exists, consider it enabled */ remote = of_graph_get_remote_port(ep); - if (remote) + if (remote) { + of_node_put(remote); + of_node_put(ep); return true; + } } return false; } -static struct regmap_config meson_regmap_config = { +static const struct regmap_config meson_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = 0x1000, }; +static void meson_vpu_init(struct meson_drm *priv) +{ + u32 value; + + /* + * Slave dc0 and dc5 connected to master port 1. + * By default other slaves are connected to master port 0. + */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) | + VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1); + writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C1)); + + /* Slave dc0 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1); + writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C2)); + + /* Slave dc4 and dc7 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) | + VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1); + writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L2C1)); + + /* Slave dc1 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1); + writel_relaxed(value, priv->io_base + _REG(VPU_WRARB_MODE_L2C1)); +} + +struct meson_drm_soc_attr { + struct meson_drm_soc_limits limits; + const struct soc_device_attribute *attrs; +}; + +static const struct meson_drm_soc_attr meson_drm_soc_attrs[] = { + /* S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz */ + { + .limits = { + .max_hdmi_phy_freq = 1650000000, + }, + .attrs = (const struct soc_device_attribute []) { + { .soc_id = "GXL (S805*)", }, + { /* sentinel */ } + } + }, +}; + static int meson_drv_bind_master(struct device *dev, bool has_components) { struct platform_device *pdev = to_platform_device(dev); + const struct meson_drm_match_data *match; struct meson_drm *priv; struct drm_device *drm; struct resource *res; void __iomem *regs; - int ret; + int ret, i; /* Checks if an output connector is available */ if (!meson_vpu_has_available_connectors(dev)) { @@ -167,6 +194,10 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) return -ENODEV; } + match = of_device_get_match_data(dev); + if (!match) + return -ENODEV; + drm = drm_dev_alloc(&meson_driver, dev); if (IS_ERR(drm)) return PTR_ERR(drm); @@ -179,101 +210,176 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) drm->dev_private = priv; priv->drm = drm; priv->dev = dev; + priv->compat = match->compat; + priv->afbcd.ops = match->afbcd_ops; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpu"); - regs = devm_ioremap_resource(dev, res); - if (IS_ERR(regs)) - return PTR_ERR(regs); + regs = devm_platform_ioremap_resource_byname(pdev, "vpu"); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto free_drm; + } priv->io_base = regs; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hhi"); + if (!res) { + ret = -EINVAL; + goto free_drm; + } /* Simply ioremap since it may be a shared register zone */ regs = devm_ioremap(dev, res->start, resource_size(res)); - if (!regs) - return -EADDRNOTAVAIL; + if (!regs) { + ret = -EADDRNOTAVAIL; + goto free_drm; + } priv->hhi = devm_regmap_init_mmio(dev, regs, &meson_regmap_config); if (IS_ERR(priv->hhi)) { dev_err(&pdev->dev, "Couldn't create the HHI regmap\n"); - return PTR_ERR(priv->hhi); + ret = PTR_ERR(priv->hhi); + goto free_drm; } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmc"); - /* Simply ioremap since it may be a shared register zone */ - regs = devm_ioremap(dev, res->start, resource_size(res)); - if (!regs) - return -EADDRNOTAVAIL; - - priv->dmc = devm_regmap_init_mmio(dev, regs, - &meson_regmap_config); - if (IS_ERR(priv->dmc)) { - dev_err(&pdev->dev, "Couldn't create the DMC regmap\n"); - return PTR_ERR(priv->dmc); + priv->canvas = meson_canvas_get(dev); + if (IS_ERR(priv->canvas)) { + ret = PTR_ERR(priv->canvas); + goto free_drm; } + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); + if (ret) + goto free_drm; + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0); + if (ret) + goto free_canvas_osd1; + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_1); + if (ret) + goto free_canvas_vd1_0; + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_2); + if (ret) + goto free_canvas_vd1_1; + priv->vsync_irq = platform_get_irq(pdev, 0); - drm_vblank_init(drm, 1); - drm_mode_config_init(drm); + ret = drm_vblank_init(drm, 1); + if (ret) + goto free_canvas_vd1_2; + + /* Assign limits per soc revision/package */ + for (i = 0 ; i < ARRAY_SIZE(meson_drm_soc_attrs) ; ++i) { + if (soc_device_match(meson_drm_soc_attrs[i].attrs)) { + priv->limits = &meson_drm_soc_attrs[i].limits; + break; + } + } + + /* + * Remove early framebuffers (ie. simplefb). The framebuffer can be + * located anywhere in RAM + */ + ret = aperture_remove_all_conflicting_devices(meson_driver.name); + if (ret) + goto free_canvas_vd1_2; + + ret = drmm_mode_config_init(drm); + if (ret) + goto free_canvas_vd1_2; drm->mode_config.max_width = 3840; drm->mode_config.max_height = 2160; drm->mode_config.funcs = &meson_mode_config_funcs; + drm->mode_config.helper_private = &meson_mode_config_helpers; /* Hardware Initialization */ + meson_vpu_init(priv); meson_venc_init(priv); meson_vpp_init(priv); meson_viu_init(priv); + if (priv->afbcd.ops) { + ret = priv->afbcd.ops->init(priv); + if (ret) + goto free_canvas_vd1_2; + } /* Encoder Initialization */ - ret = meson_venc_cvbs_create(priv); + ret = meson_encoder_cvbs_probe(priv); if (ret) - goto free_drm; + goto exit_afbcd; if (has_components) { - ret = component_bind_all(drm->dev, drm); + ret = component_bind_all(dev, drm); if (ret) { dev_err(drm->dev, "Couldn't bind all components\n"); - goto free_drm; + /* Do not try to unbind */ + has_components = false; + goto exit_afbcd; } } + ret = meson_encoder_hdmi_probe(priv); + if (ret) + goto exit_afbcd; + + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { + ret = meson_encoder_dsi_probe(priv); + if (ret) + goto exit_afbcd; + } + ret = meson_plane_create(priv); if (ret) - goto free_drm; + goto exit_afbcd; + + ret = meson_overlay_create(priv); + if (ret) + goto exit_afbcd; ret = meson_crtc_create(priv); if (ret) - goto free_drm; + goto exit_afbcd; - ret = drm_irq_install(drm, priv->vsync_irq); + ret = request_irq(priv->vsync_irq, meson_irq, 0, drm->driver->name, drm); if (ret) - goto free_drm; + goto exit_afbcd; drm_mode_config_reset(drm); - priv->fbdev = drm_fbdev_cma_init(drm, 32, - drm->mode_config.num_connector); - if (IS_ERR(priv->fbdev)) { - ret = PTR_ERR(priv->fbdev); - goto free_drm; - } - drm_kms_helper_poll_init(drm); platform_set_drvdata(pdev, priv); ret = drm_dev_register(drm, 0); if (ret) - goto free_drm; + goto uninstall_irq; + + drm_client_setup(drm, NULL); return 0; +uninstall_irq: + free_irq(priv->vsync_irq, drm); +exit_afbcd: + if (priv->afbcd.ops) + priv->afbcd.ops->exit(priv); +free_canvas_vd1_2: + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2); +free_canvas_vd1_1: + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); +free_canvas_vd1_0: + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); +free_canvas_osd1: + meson_canvas_free(priv->canvas, priv->canvas_id_osd1); free_drm: - drm_dev_unref(drm); + drm_dev_put(drm); + + meson_encoder_dsi_remove(priv); + meson_encoder_hdmi_remove(priv); + meson_encoder_cvbs_remove(priv); + + if (has_components) + component_unbind_all(dev, drm); return ret; } @@ -285,15 +391,30 @@ static int meson_drv_bind(struct device *dev) static void meson_drv_unbind(struct device *dev) { - struct drm_device *drm = dev_get_drvdata(dev); - struct meson_drm *priv = drm->dev_private; + struct meson_drm *priv = dev_get_drvdata(dev); + struct drm_device *drm = priv->drm; + + if (priv->canvas) { + meson_canvas_free(priv->canvas, priv->canvas_id_osd1); + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2); + } drm_dev_unregister(drm); drm_kms_helper_poll_fini(drm); - drm_fbdev_cma_fini(priv->fbdev); - drm_mode_config_cleanup(drm); - drm_dev_unref(drm); + drm_atomic_helper_shutdown(drm); + free_irq(priv->vsync_irq, drm); + drm_dev_put(drm); + + meson_encoder_dsi_remove(priv); + meson_encoder_hdmi_remove(priv); + meson_encoder_cvbs_remove(priv); + component_unbind_all(dev, drm); + + if (priv->afbcd.ops) + priv->afbcd.ops->exit(priv); } static const struct component_master_ops meson_drv_master_ops = { @@ -301,53 +422,58 @@ static const struct component_master_ops meson_drv_master_ops = { .unbind = meson_drv_unbind, }; -static int compare_of(struct device *dev, void *data) +static int __maybe_unused meson_drv_pm_suspend(struct device *dev) { - DRM_DEBUG_DRIVER("Comparing of node %s with %s\n", - of_node_full_name(dev->of_node), - of_node_full_name(data)); + struct meson_drm *priv = dev_get_drvdata(dev); - return dev->of_node == data; -} + if (!priv) + return 0; -/* Possible connectors nodes to ignore */ -static const struct of_device_id connectors_match[] = { - { .compatible = "composite-video-connector" }, - { .compatible = "svideo-connector" }, - { .compatible = "hdmi-connector" }, - { .compatible = "dvi-connector" }, - {} -}; + return drm_mode_config_helper_suspend(priv->drm); +} -static int meson_probe_remote(struct platform_device *pdev, - struct component_match **match, - struct device_node *parent, - struct device_node *remote) +static int __maybe_unused meson_drv_pm_resume(struct device *dev) { - struct device_node *ep, *remote_node; - int count = 1; + struct meson_drm *priv = dev_get_drvdata(dev); - /* If node is a connector, return and do not add to match table */ - if (of_match_node(connectors_match, remote)) - return 1; + if (!priv) + return 0; - component_match_add(&pdev->dev, match, compare_of, remote); + meson_vpu_init(priv); + meson_venc_init(priv); + meson_vpp_init(priv); + meson_viu_init(priv); + if (priv->afbcd.ops) + priv->afbcd.ops->init(priv); - for_each_endpoint_of_node(remote, ep) { - remote_node = of_graph_get_remote_port_parent(ep); - if (!remote_node || - remote_node == parent || /* Ignore parent endpoint */ - !of_device_is_available(remote_node)) - continue; + return drm_mode_config_helper_resume(priv->drm); +} - count += meson_probe_remote(pdev, match, remote, remote_node); +static void meson_drv_shutdown(struct platform_device *pdev) +{ + struct meson_drm *priv = dev_get_drvdata(&pdev->dev); - of_node_put(remote_node); - } + if (!priv) + return; - return count; + drm_kms_helper_poll_fini(priv->drm); + drm_atomic_helper_shutdown(priv->drm); } +/* + * Only devices to use as components + * TOFIX: get rid of components when we can finally + * get meson_dx_hdmi to stop using the meson_drm + * private structure for HHI registers. + */ +static const struct of_device_id components_dev_match[] = { + { .compatible = "amlogic,meson-gxbb-dw-hdmi" }, + { .compatible = "amlogic,meson-gxl-dw-hdmi" }, + { .compatible = "amlogic,meson-gxm-dw-hdmi" }, + { .compatible = "amlogic,meson-g12a-dw-hdmi" }, + {} +}; + static int meson_drv_probe(struct platform_device *pdev) { struct component_match *match = NULL; @@ -357,10 +483,21 @@ static int meson_drv_probe(struct platform_device *pdev) for_each_endpoint_of_node(np, ep) { remote = of_graph_get_remote_port_parent(ep); - if (!remote || !of_device_is_available(remote)) + if (!remote || !of_device_is_available(remote)) { + of_node_put(remote); continue; + } + + if (of_match_node(components_dev_match, remote)) { + component_match_add(&pdev->dev, &match, component_compare_of, remote); + + dev_dbg(&pdev->dev, "parent %pOF remote match add %pOF parent %s\n", + np, remote, dev_name(&pdev->dev)); + } + + of_node_put(remote); - count += meson_probe_remote(pdev, &match, np, remote); + ++count; } if (count && !match) @@ -379,23 +516,58 @@ static int meson_drv_probe(struct platform_device *pdev) return 0; }; +static void meson_drv_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &meson_drv_master_ops); +} + +static struct meson_drm_match_data meson_drm_gxbb_data = { + .compat = VPU_COMPATIBLE_GXBB, +}; + +static struct meson_drm_match_data meson_drm_gxl_data = { + .compat = VPU_COMPATIBLE_GXL, +}; + +static struct meson_drm_match_data meson_drm_gxm_data = { + .compat = VPU_COMPATIBLE_GXM, + .afbcd_ops = &meson_afbcd_gxm_ops, +}; + +static struct meson_drm_match_data meson_drm_g12a_data = { + .compat = VPU_COMPATIBLE_G12A, + .afbcd_ops = &meson_afbcd_g12a_ops, +}; + static const struct of_device_id dt_match[] = { - { .compatible = "amlogic,meson-gxbb-vpu" }, - { .compatible = "amlogic,meson-gxl-vpu" }, - { .compatible = "amlogic,meson-gxm-vpu" }, + { .compatible = "amlogic,meson-gxbb-vpu", + .data = (void *)&meson_drm_gxbb_data }, + { .compatible = "amlogic,meson-gxl-vpu", + .data = (void *)&meson_drm_gxl_data }, + { .compatible = "amlogic,meson-gxm-vpu", + .data = (void *)&meson_drm_gxm_data }, + { .compatible = "amlogic,meson-g12a-vpu", + .data = (void *)&meson_drm_g12a_data }, {} }; MODULE_DEVICE_TABLE(of, dt_match); +static const struct dev_pm_ops meson_drv_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(meson_drv_pm_suspend, meson_drv_pm_resume) +}; + static struct platform_driver meson_drm_platform_driver = { .probe = meson_drv_probe, + .remove = meson_drv_remove, + .shutdown = meson_drv_shutdown, .driver = { .name = "meson-drm", .of_match_table = dt_match, + .pm = &meson_drv_pm_ops, }, }; -module_platform_driver(meson_drm_platform_driver); +drm_module_platform_driver(meson_drm_platform_driver); MODULE_AUTHOR("Jasper St. Pierre <jstpierre@mecheye.net>"); MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); |
