diff options
author | Paul Kocialkowski <paul.kocialkowski@bootlin.com> | 2022-05-20 16:15:55 +0200 |
---|---|---|
committer | Paul Kocialkowski <paul.kocialkowski@bootlin.com> | 2022-06-09 16:49:56 +0200 |
commit | efeeaefe9be56e8ae5e5b4e9ff6d2275ec977ec5 (patch) | |
tree | fd6f9768de34219c02f0186f5c188d663fc19bb6 /drivers/gpu/drm/logicvc/logicvc_drm.c | |
parent | bdde97ac4bea26d48f60e353396b2b4fb0234ac4 (diff) |
drm: Add support for the LogiCVC display controller
Introduces a driver for the LogiCVC display controller, a programmable
logic controller optimized for use in Xilinx Zynq-7000 SoCs and other
Xilinx FPGAs. The controller is mostly configured at logic synthesis
time so only a subset of configuration is left for the driver to
handle.
The following features are implemented and tested:
- LVDS 4-bit interface;
- RGB565 pixel formats;
- Multiple layers and hardware composition;
- Layer-wide alpha mode;
The following features are implemented but untested:
- Other RGB pixel formats;
- Layer framebuffer configuration for version 4;
- Lowest-layer used as background color;
- Per-pixel alpha mode.
The following features are not implemented:
- YUV pixel formats;
- DVI, LVDS 3-bit, ITU656 and camera link interfaces;
- External parallel input for layer;
- Color-keying;
- LUT-based alpha modes.
Additional implementation-specific notes:
- Panels are only enabled after the first page flip to avoid flashing a
white screen.
- Depth used in context of the LogiCVC driver only counts color components
to match the definition of the synthesis parameters.
Support is implemented for both version 3 and 4 of the controller.
With version 3, framebuffers are stored in a dedicated contiguous
memory area, with a base address hardcoded for each layer. This requires
using a dedicated CMA pool registered at the base address and tweaking a
few offset-related registers to try to use any buffer allocated from
the pool. This is done on a best-effort basis to have the hardware cope
with the DRM framebuffer allocation model and there is no guarantee
that each buffer allocated by GEM CMA can be used for any layer.
In particular, buffers allocated below the base address for a layer are
guaranteed not to be configurable for that layer. See the implementation of
logicvc_layer_buffer_find_setup for specifics.
Version 4 allows configuring each buffer address directly, which
guarantees that any buffer can be configured.
Signed-off-by: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20220520141555.1429041-2-paul.kocialkowski@bootlin.com
Diffstat (limited to 'drivers/gpu/drm/logicvc/logicvc_drm.c')
-rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_drm.c | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/drivers/gpu/drm/logicvc/logicvc_drm.c b/drivers/gpu/drm/logicvc/logicvc_drm.c new file mode 100644 index 0000000000000..df1805cf0f952 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_drm.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_reserved_mem.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_print.h> + +#include "logicvc_crtc.h" +#include "logicvc_drm.h" +#include "logicvc_interface.h" +#include "logicvc_mode.h" +#include "logicvc_layer.h" +#include "logicvc_of.h" +#include "logicvc_regs.h" + +DEFINE_DRM_GEM_CMA_FOPS(logicvc_drm_fops); + +static int logicvc_drm_gem_cma_dumb_create(struct drm_file *file_priv, + struct drm_device *drm_dev, + struct drm_mode_create_dumb *args) +{ + struct logicvc_drm *logicvc = logicvc_drm(drm_dev); + + /* Stride is always fixed to its configuration value. */ + args->pitch = logicvc->config.row_stride * DIV_ROUND_UP(args->bpp, 8); + + return drm_gem_cma_dumb_create_internal(file_priv, drm_dev, args); +} + +static struct drm_driver logicvc_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | + DRIVER_ATOMIC, + + .fops = &logicvc_drm_fops, + .name = "logicvc-drm", + .desc = "Xylon LogiCVC DRM driver", + .date = "20200403", + .major = 1, + .minor = 0, + + DRM_GEM_CMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(logicvc_drm_gem_cma_dumb_create), +}; + +static struct regmap_config logicvc_drm_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .name = "logicvc-drm", +}; + +static irqreturn_t logicvc_drm_irq_handler(int irq, void *data) +{ + struct logicvc_drm *logicvc = data; + irqreturn_t ret = IRQ_NONE; + u32 stat = 0; + + /* Get pending interrupt sources. */ + regmap_read(logicvc->regmap, LOGICVC_INT_STAT_REG, &stat); + + /* Clear all pending interrupt sources. */ + regmap_write(logicvc->regmap, LOGICVC_INT_STAT_REG, stat); + + if (stat & LOGICVC_INT_STAT_V_SYNC) { + logicvc_crtc_vblank_handler(logicvc); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int logicvc_drm_config_parse(struct logicvc_drm *logicvc) +{ + struct drm_device *drm_dev = &logicvc->drm_dev; + struct device *dev = drm_dev->dev; + struct device_node *of_node = dev->of_node; + struct logicvc_drm_config *config = &logicvc->config; + struct device_node *layers_node; + int ret; + + logicvc_of_property_parse_bool(of_node, LOGICVC_OF_PROPERTY_DITHERING, + &config->dithering); + logicvc_of_property_parse_bool(of_node, + LOGICVC_OF_PROPERTY_BACKGROUND_LAYER, + &config->background_layer); + logicvc_of_property_parse_bool(of_node, + LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE, + &config->layers_configurable); + + ret = logicvc_of_property_parse_u32(of_node, + LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE, + &config->display_interface); + if (ret) + return ret; + + ret = logicvc_of_property_parse_u32(of_node, + LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE, + &config->display_colorspace); + if (ret) + return ret; + + ret = logicvc_of_property_parse_u32(of_node, + LOGICVC_OF_PROPERTY_DISPLAY_DEPTH, + &config->display_depth); + if (ret) + return ret; + + ret = logicvc_of_property_parse_u32(of_node, + LOGICVC_OF_PROPERTY_ROW_STRIDE, + &config->row_stride); + if (ret) + return ret; + + layers_node = of_get_child_by_name(of_node, "layers"); + if (!layers_node) { + drm_err(drm_dev, "Missing non-optional layers node\n"); + return -EINVAL; + } + + config->layers_count = of_get_child_count(layers_node); + if (!config->layers_count) { + drm_err(drm_dev, + "Missing a non-optional layers children node\n"); + return -EINVAL; + } + + return 0; +} + +static int logicvc_clocks_prepare(struct logicvc_drm *logicvc) +{ + struct drm_device *drm_dev = &logicvc->drm_dev; + struct device *dev = drm_dev->dev; + + struct { + struct clk **clk; + char *name; + bool optional; + } clocks_map[] = { + { + .clk = &logicvc->vclk, + .name = "vclk", + .optional = false, + }, + { + .clk = &logicvc->vclk2, + .name = "vclk2", + .optional = true, + }, + { + .clk = &logicvc->lvdsclk, + .name = "lvdsclk", + .optional = true, + }, + { + .clk = &logicvc->lvdsclkn, + .name = "lvdsclkn", + .optional = true, + }, + }; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(clocks_map); i++) { + struct clk *clk; + + clk = devm_clk_get(dev, clocks_map[i].name); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) == -ENOENT && clocks_map[i].optional) + continue; + + drm_err(drm_dev, "Missing non-optional clock %s\n", + clocks_map[i].name); + + ret = PTR_ERR(clk); + goto error; + } + + ret = clk_prepare_enable(clk); + if (ret) { + drm_err(drm_dev, + "Failed to prepare and enable clock %s\n", + clocks_map[i].name); + goto error; + } + + *clocks_map[i].clk = clk; + } + + return 0; + +error: + for (i = 0; i < ARRAY_SIZE(clocks_map); i++) { + if (!*clocks_map[i].clk) + continue; + + clk_disable_unprepare(*clocks_map[i].clk); + *clocks_map[i].clk = NULL; + } + + return ret; +} + +static int logicvc_clocks_unprepare(struct logicvc_drm *logicvc) +{ + struct clk **clocks[] = { + &logicvc->vclk, + &logicvc->vclk2, + &logicvc->lvdsclk, + &logicvc->lvdsclkn, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(clocks); i++) { + if (!*clocks[i]) + continue; + + clk_disable_unprepare(*clocks[i]); + *clocks[i] = NULL; + } + + return 0; +} + +static const struct logicvc_drm_caps logicvc_drm_caps[] = { + { + .major = 3, + .layer_address = false, + }, + { + .major = 4, + .layer_address = true, + }, + { + .major = 5, + .layer_address = true, + }, +}; + +static const struct logicvc_drm_caps * +logicvc_drm_caps_match(struct logicvc_drm *logicvc) +{ + struct drm_device *drm_dev = &logicvc->drm_dev; + const struct logicvc_drm_caps *caps = NULL; + unsigned int major, minor; + char level; + unsigned int i; + u32 version; + + regmap_read(logicvc->regmap, LOGICVC_IP_VERSION_REG, &version); + + major = FIELD_GET(LOGICVC_IP_VERSION_MAJOR_MASK, version); + minor = FIELD_GET(LOGICVC_IP_VERSION_MINOR_MASK, version); + level = FIELD_GET(LOGICVC_IP_VERSION_LEVEL_MASK, version) + 'a'; + + for (i = 0; i < ARRAY_SIZE(logicvc_drm_caps); i++) { + if (logicvc_drm_caps[i].major && + logicvc_drm_caps[i].major != major) + continue; + + if (logicvc_drm_caps[i].minor && + logicvc_drm_caps[i].minor != minor) + continue; + + if (logicvc_drm_caps[i].level && + logicvc_drm_caps[i].level != level) + continue; + + caps = &logicvc_drm_caps[i]; + } + + drm_info(drm_dev, "LogiCVC version %d.%02d.%c\n", major, minor, level); + + return caps; +} + +static int logicvc_drm_probe(struct platform_device *pdev) +{ + struct device_node *of_node = pdev->dev.of_node; + struct device_node *reserved_mem_node; + struct reserved_mem *reserved_mem = NULL; + const struct logicvc_drm_caps *caps; + struct logicvc_drm *logicvc; + struct device *dev = &pdev->dev; + struct drm_device *drm_dev; + struct regmap *regmap; + struct resource res; + void __iomem *base; + int irq; + int ret; + + ret = of_reserved_mem_device_init(dev); + if (ret && ret != -ENODEV) { + dev_err(dev, "Failed to init memory region\n"); + goto error_early; + } + + reserved_mem_node = of_parse_phandle(of_node, "memory-region", 0); + if (reserved_mem_node) { + reserved_mem = of_reserved_mem_lookup(reserved_mem_node); + of_node_put(reserved_mem_node); + } + + /* Get regmap from parent if available. */ + if (of_node->parent) + regmap = syscon_node_to_regmap(of_node->parent); + + /* Register our own regmap otherwise. */ + if (IS_ERR_OR_NULL(regmap)) { + ret = of_address_to_resource(of_node, 0, &res); + if (ret) { + dev_err(dev, "Failed to get resource from address\n"); + goto error_reserved_mem; + } + + base = devm_ioremap_resource(dev, &res); + if (IS_ERR(base)) { + dev_err(dev, "Failed to map I/O base\n"); + ret = PTR_ERR(base); + goto error_reserved_mem; + } + + logicvc_drm_regmap_config.max_register = resource_size(&res) - + 4; + + regmap = devm_regmap_init_mmio(dev, base, + &logicvc_drm_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to create regmap for I/O\n"); + ret = PTR_ERR(regmap); + goto error_reserved_mem; + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "Failed to get IRQ\n"); + ret = -ENODEV; + goto error_reserved_mem; + } + + logicvc = devm_drm_dev_alloc(dev, &logicvc_drm_driver, + struct logicvc_drm, drm_dev); + if (IS_ERR(logicvc)) { + ret = PTR_ERR(logicvc); + goto error_reserved_mem; + } + + platform_set_drvdata(pdev, logicvc); + drm_dev = &logicvc->drm_dev; + + logicvc->regmap = regmap; + INIT_LIST_HEAD(&logicvc->layers_list); + + caps = logicvc_drm_caps_match(logicvc); + if (!caps) { + ret = -EINVAL; + goto error_reserved_mem; + } + + logicvc->caps = caps; + + if (reserved_mem) + logicvc->reserved_mem_base = reserved_mem->base; + + ret = logicvc_clocks_prepare(logicvc); + if (ret) { + drm_err(drm_dev, "Failed to prepare clocks\n"); + goto error_reserved_mem; + } + + ret = devm_request_irq(dev, irq, logicvc_drm_irq_handler, 0, + dev_name(dev), logicvc); + if (ret) { + drm_err(drm_dev, "Failed to request IRQ\n"); + goto error_clocks; + } + + ret = logicvc_drm_config_parse(logicvc); + if (ret && ret != -ENODEV) { + drm_err(drm_dev, "Failed to parse config\n"); + goto error_clocks; + } + + ret = drmm_mode_config_init(drm_dev); + if (ret) { + drm_err(drm_dev, "Failed to init mode config\n"); + goto error_clocks; + } + + ret = logicvc_layers_init(logicvc); + if (ret) { + drm_err(drm_dev, "Failed to initialize layers\n"); + goto error_clocks; + } + + ret = logicvc_crtc_init(logicvc); + if (ret) { + drm_err(drm_dev, "Failed to initialize CRTC\n"); + goto error_clocks; + } + + logicvc_layers_attach_crtc(logicvc); + + ret = logicvc_interface_init(logicvc); + if (ret) { + if (ret != -EPROBE_DEFER) + drm_err(drm_dev, "Failed to initialize interface\n"); + + goto error_clocks; + } + + logicvc_interface_attach_crtc(logicvc); + + ret = logicvc_mode_init(logicvc); + if (ret) { + drm_err(drm_dev, "Failed to initialize KMS\n"); + goto error_clocks; + } + + ret = drm_dev_register(drm_dev, 0); + if (ret) { + drm_err(drm_dev, "Failed to register DRM device\n"); + goto error_mode; + } + + drm_fbdev_generic_setup(drm_dev, drm_dev->mode_config.preferred_depth); + + return 0; + +error_mode: + logicvc_mode_fini(logicvc); + +error_clocks: + logicvc_clocks_unprepare(logicvc); + +error_reserved_mem: + of_reserved_mem_device_release(dev); + +error_early: + return ret; +} + +static int logicvc_drm_remove(struct platform_device *pdev) +{ + struct logicvc_drm *logicvc = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct drm_device *drm_dev = &logicvc->drm_dev; + + drm_dev_unregister(drm_dev); + drm_atomic_helper_shutdown(drm_dev); + + logicvc_mode_fini(logicvc); + + logicvc_clocks_unprepare(logicvc); + + of_reserved_mem_device_release(dev); + + return 0; +} + +static const struct of_device_id logicvc_drm_of_table[] = { + { .compatible = "xylon,logicvc-3.02.a-display" }, + { .compatible = "xylon,logicvc-4.01.a-display" }, + {}, +}; +MODULE_DEVICE_TABLE(of, logicvc_drm_of_table); + +static struct platform_driver logicvc_drm_platform_driver = { + .probe = logicvc_drm_probe, + .remove = logicvc_drm_remove, + .driver = { + .name = "logicvc-drm", + .of_match_table = logicvc_drm_of_table, + }, +}; + +module_platform_driver(logicvc_drm_platform_driver); + +MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>"); +MODULE_DESCRIPTION("Xylon LogiCVC DRM driver"); +MODULE_LICENSE("GPL"); |