575 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (C) 2016 BayLibre, SAS4 * Author: Neil Armstrong <narmstrong@baylibre.com>5 * Copyright (C) 2014 Endless Mobile6 *7 * Written by:8 * Jasper St. Pierre <jstpierre@mecheye.net>9 */10 11#include <linux/component.h>12#include <linux/module.h>13#include <linux/of_graph.h>14#include <linux/sys_soc.h>15#include <linux/platform_device.h>16#include <linux/soc/amlogic/meson-canvas.h>17 18#include <drm/drm_aperture.h>19#include <drm/drm_atomic_helper.h>20#include <drm/drm_drv.h>21#include <drm/drm_fbdev_dma.h>22#include <drm/drm_gem_dma_helper.h>23#include <drm/drm_gem_framebuffer_helper.h>24#include <drm/drm_modeset_helper_vtables.h>25#include <drm/drm_module.h>26#include <drm/drm_probe_helper.h>27#include <drm/drm_vblank.h>28 29#include "meson_crtc.h"30#include "meson_drv.h"31#include "meson_overlay.h"32#include "meson_plane.h"33#include "meson_osd_afbcd.h"34#include "meson_registers.h"35#include "meson_encoder_cvbs.h"36#include "meson_encoder_hdmi.h"37#include "meson_encoder_dsi.h"38#include "meson_viu.h"39#include "meson_vpp.h"40#include "meson_rdma.h"41 42#define DRIVER_NAME "meson"43#define DRIVER_DESC "Amlogic Meson DRM driver"44 45/**46 * DOC: Video Processing Unit47 *48 * VPU Handles the Global Video Processing, it includes management of the49 * clocks gates, blocks reset lines and power domains.50 *51 * What is missing :52 *53 * - Full reset of entire video processing HW blocks54 * - Scaling and setup of the VPU clock55 * - Bus clock gates56 * - Powering up video processing HW blocks57 * - Powering Up HDMI controller and PHY58 */59 60static const struct drm_mode_config_funcs meson_mode_config_funcs = {61 .atomic_check = drm_atomic_helper_check,62 .atomic_commit = drm_atomic_helper_commit,63 .fb_create = drm_gem_fb_create,64};65 66static const struct drm_mode_config_helper_funcs meson_mode_config_helpers = {67 .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,68};69 70static irqreturn_t meson_irq(int irq, void *arg)71{72 struct drm_device *dev = arg;73 struct meson_drm *priv = dev->dev_private;74 75 (void)readl_relaxed(priv->io_base + _REG(VENC_INTFLAG));76 77 meson_crtc_irq(priv);78 79 return IRQ_HANDLED;80}81 82static int meson_dumb_create(struct drm_file *file, struct drm_device *dev,83 struct drm_mode_create_dumb *args)84{85 /*86 * We need 64bytes aligned stride, and PAGE aligned size87 */88 args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), SZ_64);89 args->size = PAGE_ALIGN(args->pitch * args->height);90 91 return drm_gem_dma_dumb_create_internal(file, dev, args);92}93 94DEFINE_DRM_GEM_DMA_FOPS(fops);95 96static const struct drm_driver meson_driver = {97 .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,98 99 /* DMA Ops */100 DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(meson_dumb_create),101 102 /* Misc */103 .fops = &fops,104 .name = DRIVER_NAME,105 .desc = DRIVER_DESC,106 .date = "20161109",107 .major = 1,108 .minor = 0,109};110 111static bool meson_vpu_has_available_connectors(struct device *dev)112{113 struct device_node *ep, *remote;114 115 /* Parses each endpoint and check if remote exists */116 for_each_endpoint_of_node(dev->of_node, ep) {117 /* If the endpoint node exists, consider it enabled */118 remote = of_graph_get_remote_port(ep);119 if (remote) {120 of_node_put(remote);121 of_node_put(ep);122 return true;123 }124 }125 126 return false;127}128 129static struct regmap_config meson_regmap_config = {130 .reg_bits = 32,131 .val_bits = 32,132 .reg_stride = 4,133 .max_register = 0x1000,134};135 136static void meson_vpu_init(struct meson_drm *priv)137{138 u32 value;139 140 /*141 * Slave dc0 and dc5 connected to master port 1.142 * By default other slaves are connected to master port 0.143 */144 value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) |145 VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1);146 writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C1));147 148 /* Slave dc0 connected to master port 1 */149 value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1);150 writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C2));151 152 /* Slave dc4 and dc7 connected to master port 1 */153 value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) |154 VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1);155 writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L2C1));156 157 /* Slave dc1 connected to master port 1 */158 value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1);159 writel_relaxed(value, priv->io_base + _REG(VPU_WRARB_MODE_L2C1));160}161 162struct meson_drm_soc_attr {163 struct meson_drm_soc_limits limits;164 const struct soc_device_attribute *attrs;165};166 167static const struct meson_drm_soc_attr meson_drm_soc_attrs[] = {168 /* S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz */169 {170 .limits = {171 .max_hdmi_phy_freq = 1650000,172 },173 .attrs = (const struct soc_device_attribute []) {174 { .soc_id = "GXL (S805*)", },175 { /* sentinel */ }176 }177 },178};179 180static int meson_drv_bind_master(struct device *dev, bool has_components)181{182 struct platform_device *pdev = to_platform_device(dev);183 const struct meson_drm_match_data *match;184 struct meson_drm *priv;185 struct drm_device *drm;186 struct resource *res;187 void __iomem *regs;188 int ret, i;189 190 /* Checks if an output connector is available */191 if (!meson_vpu_has_available_connectors(dev)) {192 dev_err(dev, "No output connector available\n");193 return -ENODEV;194 }195 196 match = of_device_get_match_data(dev);197 if (!match)198 return -ENODEV;199 200 drm = drm_dev_alloc(&meson_driver, dev);201 if (IS_ERR(drm))202 return PTR_ERR(drm);203 204 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);205 if (!priv) {206 ret = -ENOMEM;207 goto free_drm;208 }209 drm->dev_private = priv;210 priv->drm = drm;211 priv->dev = dev;212 priv->compat = match->compat;213 priv->afbcd.ops = match->afbcd_ops;214 215 regs = devm_platform_ioremap_resource_byname(pdev, "vpu");216 if (IS_ERR(regs)) {217 ret = PTR_ERR(regs);218 goto free_drm;219 }220 221 priv->io_base = regs;222 223 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hhi");224 if (!res) {225 ret = -EINVAL;226 goto free_drm;227 }228 /* Simply ioremap since it may be a shared register zone */229 regs = devm_ioremap(dev, res->start, resource_size(res));230 if (!regs) {231 ret = -EADDRNOTAVAIL;232 goto free_drm;233 }234 235 priv->hhi = devm_regmap_init_mmio(dev, regs,236 &meson_regmap_config);237 if (IS_ERR(priv->hhi)) {238 dev_err(&pdev->dev, "Couldn't create the HHI regmap\n");239 ret = PTR_ERR(priv->hhi);240 goto free_drm;241 }242 243 priv->canvas = meson_canvas_get(dev);244 if (IS_ERR(priv->canvas)) {245 ret = PTR_ERR(priv->canvas);246 goto free_drm;247 }248 249 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1);250 if (ret)251 goto free_drm;252 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0);253 if (ret)254 goto free_canvas_osd1;255 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_1);256 if (ret)257 goto free_canvas_vd1_0;258 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_2);259 if (ret)260 goto free_canvas_vd1_1;261 262 priv->vsync_irq = platform_get_irq(pdev, 0);263 264 ret = drm_vblank_init(drm, 1);265 if (ret)266 goto free_canvas_vd1_2;267 268 /* Assign limits per soc revision/package */269 for (i = 0 ; i < ARRAY_SIZE(meson_drm_soc_attrs) ; ++i) {270 if (soc_device_match(meson_drm_soc_attrs[i].attrs)) {271 priv->limits = &meson_drm_soc_attrs[i].limits;272 break;273 }274 }275 276 /*277 * Remove early framebuffers (ie. simplefb). The framebuffer can be278 * located anywhere in RAM279 */280 ret = drm_aperture_remove_framebuffers(&meson_driver);281 if (ret)282 goto free_canvas_vd1_2;283 284 ret = drmm_mode_config_init(drm);285 if (ret)286 goto free_canvas_vd1_2;287 drm->mode_config.max_width = 3840;288 drm->mode_config.max_height = 2160;289 drm->mode_config.funcs = &meson_mode_config_funcs;290 drm->mode_config.helper_private = &meson_mode_config_helpers;291 292 /* Hardware Initialization */293 294 meson_vpu_init(priv);295 meson_venc_init(priv);296 meson_vpp_init(priv);297 meson_viu_init(priv);298 if (priv->afbcd.ops) {299 ret = priv->afbcd.ops->init(priv);300 if (ret)301 goto free_canvas_vd1_2;302 }303 304 /* Encoder Initialization */305 306 ret = meson_encoder_cvbs_probe(priv);307 if (ret)308 goto exit_afbcd;309 310 if (has_components) {311 ret = component_bind_all(dev, drm);312 if (ret) {313 dev_err(drm->dev, "Couldn't bind all components\n");314 /* Do not try to unbind */315 has_components = false;316 goto exit_afbcd;317 }318 }319 320 ret = meson_encoder_hdmi_probe(priv);321 if (ret)322 goto exit_afbcd;323 324 if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {325 ret = meson_encoder_dsi_probe(priv);326 if (ret)327 goto exit_afbcd;328 }329 330 ret = meson_plane_create(priv);331 if (ret)332 goto exit_afbcd;333 334 ret = meson_overlay_create(priv);335 if (ret)336 goto exit_afbcd;337 338 ret = meson_crtc_create(priv);339 if (ret)340 goto exit_afbcd;341 342 ret = request_irq(priv->vsync_irq, meson_irq, 0, drm->driver->name, drm);343 if (ret)344 goto exit_afbcd;345 346 drm_mode_config_reset(drm);347 348 drm_kms_helper_poll_init(drm);349 350 platform_set_drvdata(pdev, priv);351 352 ret = drm_dev_register(drm, 0);353 if (ret)354 goto uninstall_irq;355 356 drm_fbdev_dma_setup(drm, 32);357 358 return 0;359 360uninstall_irq:361 free_irq(priv->vsync_irq, drm);362exit_afbcd:363 if (priv->afbcd.ops)364 priv->afbcd.ops->exit(priv);365free_canvas_vd1_2:366 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2);367free_canvas_vd1_1:368 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1);369free_canvas_vd1_0:370 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0);371free_canvas_osd1:372 meson_canvas_free(priv->canvas, priv->canvas_id_osd1);373free_drm:374 drm_dev_put(drm);375 376 meson_encoder_dsi_remove(priv);377 meson_encoder_hdmi_remove(priv);378 meson_encoder_cvbs_remove(priv);379 380 if (has_components)381 component_unbind_all(dev, drm);382 383 return ret;384}385 386static int meson_drv_bind(struct device *dev)387{388 return meson_drv_bind_master(dev, true);389}390 391static void meson_drv_unbind(struct device *dev)392{393 struct meson_drm *priv = dev_get_drvdata(dev);394 struct drm_device *drm = priv->drm;395 396 if (priv->canvas) {397 meson_canvas_free(priv->canvas, priv->canvas_id_osd1);398 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0);399 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1);400 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2);401 }402 403 drm_dev_unregister(drm);404 drm_kms_helper_poll_fini(drm);405 drm_atomic_helper_shutdown(drm);406 free_irq(priv->vsync_irq, drm);407 drm_dev_put(drm);408 409 meson_encoder_dsi_remove(priv);410 meson_encoder_hdmi_remove(priv);411 meson_encoder_cvbs_remove(priv);412 413 component_unbind_all(dev, drm);414 415 if (priv->afbcd.ops)416 priv->afbcd.ops->exit(priv);417}418 419static const struct component_master_ops meson_drv_master_ops = {420 .bind = meson_drv_bind,421 .unbind = meson_drv_unbind,422};423 424static int __maybe_unused meson_drv_pm_suspend(struct device *dev)425{426 struct meson_drm *priv = dev_get_drvdata(dev);427 428 if (!priv)429 return 0;430 431 return drm_mode_config_helper_suspend(priv->drm);432}433 434static int __maybe_unused meson_drv_pm_resume(struct device *dev)435{436 struct meson_drm *priv = dev_get_drvdata(dev);437 438 if (!priv)439 return 0;440 441 meson_vpu_init(priv);442 meson_venc_init(priv);443 meson_vpp_init(priv);444 meson_viu_init(priv);445 if (priv->afbcd.ops)446 priv->afbcd.ops->init(priv);447 448 return drm_mode_config_helper_resume(priv->drm);449}450 451static void meson_drv_shutdown(struct platform_device *pdev)452{453 struct meson_drm *priv = dev_get_drvdata(&pdev->dev);454 455 if (!priv)456 return;457 458 drm_kms_helper_poll_fini(priv->drm);459 drm_atomic_helper_shutdown(priv->drm);460}461 462/*463 * Only devices to use as components464 * TOFIX: get rid of components when we can finally465 * get meson_dx_hdmi to stop using the meson_drm466 * private structure for HHI registers.467 */468static const struct of_device_id components_dev_match[] = {469 { .compatible = "amlogic,meson-gxbb-dw-hdmi" },470 { .compatible = "amlogic,meson-gxl-dw-hdmi" },471 { .compatible = "amlogic,meson-gxm-dw-hdmi" },472 { .compatible = "amlogic,meson-g12a-dw-hdmi" },473 {}474};475 476static int meson_drv_probe(struct platform_device *pdev)477{478 struct component_match *match = NULL;479 struct device_node *np = pdev->dev.of_node;480 struct device_node *ep, *remote;481 int count = 0;482 483 for_each_endpoint_of_node(np, ep) {484 remote = of_graph_get_remote_port_parent(ep);485 if (!remote || !of_device_is_available(remote)) {486 of_node_put(remote);487 continue;488 }489 490 if (of_match_node(components_dev_match, remote)) {491 component_match_add(&pdev->dev, &match, component_compare_of, remote);492 493 dev_dbg(&pdev->dev, "parent %pOF remote match add %pOF parent %s\n",494 np, remote, dev_name(&pdev->dev));495 }496 497 of_node_put(remote);498 499 ++count;500 }501 502 if (count && !match)503 return meson_drv_bind_master(&pdev->dev, false);504 505 /* If some endpoints were found, initialize the nodes */506 if (count) {507 dev_info(&pdev->dev, "Queued %d outputs on vpu\n", count);508 509 return component_master_add_with_match(&pdev->dev,510 &meson_drv_master_ops,511 match);512 }513 514 /* If no output endpoints were available, simply bail out */515 return 0;516};517 518static void meson_drv_remove(struct platform_device *pdev)519{520 component_master_del(&pdev->dev, &meson_drv_master_ops);521}522 523static struct meson_drm_match_data meson_drm_gxbb_data = {524 .compat = VPU_COMPATIBLE_GXBB,525};526 527static struct meson_drm_match_data meson_drm_gxl_data = {528 .compat = VPU_COMPATIBLE_GXL,529};530 531static struct meson_drm_match_data meson_drm_gxm_data = {532 .compat = VPU_COMPATIBLE_GXM,533 .afbcd_ops = &meson_afbcd_gxm_ops,534};535 536static struct meson_drm_match_data meson_drm_g12a_data = {537 .compat = VPU_COMPATIBLE_G12A,538 .afbcd_ops = &meson_afbcd_g12a_ops,539};540 541static const struct of_device_id dt_match[] = {542 { .compatible = "amlogic,meson-gxbb-vpu",543 .data = (void *)&meson_drm_gxbb_data },544 { .compatible = "amlogic,meson-gxl-vpu",545 .data = (void *)&meson_drm_gxl_data },546 { .compatible = "amlogic,meson-gxm-vpu",547 .data = (void *)&meson_drm_gxm_data },548 { .compatible = "amlogic,meson-g12a-vpu",549 .data = (void *)&meson_drm_g12a_data },550 {}551};552MODULE_DEVICE_TABLE(of, dt_match);553 554static const struct dev_pm_ops meson_drv_pm_ops = {555 SET_SYSTEM_SLEEP_PM_OPS(meson_drv_pm_suspend, meson_drv_pm_resume)556};557 558static struct platform_driver meson_drm_platform_driver = {559 .probe = meson_drv_probe,560 .remove_new = meson_drv_remove,561 .shutdown = meson_drv_shutdown,562 .driver = {563 .name = "meson-drm",564 .of_match_table = dt_match,565 .pm = &meson_drv_pm_ops,566 },567};568 569drm_module_platform_driver(meson_drm_platform_driver);570 571MODULE_AUTHOR("Jasper St. Pierre <jstpierre@mecheye.net>");572MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");573MODULE_DESCRIPTION(DRIVER_DESC);574MODULE_LICENSE("GPL");575