722d4f06e5
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it as merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Signed-off-by: Rob Herring <robh@kernel.org> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Steven Price <steven.price@arm.com> Acked-by: Liviu Dudau <liviu.dudau@arm.com> Reviewed-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com> Acked-by: Robert Foss <rfoss@kernel.org> Signed-off-by: Thierry Reding <treding@nvidia.com> Link: https://patchwork.freedesktop.org/patch/msgid/20230714174545.4056287-1-robh@kernel.org
506 lines
11 KiB
C
506 lines
11 KiB
C
// 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_reserved_mem.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/types.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_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_DMA_FOPS(logicvc_drm_fops);
|
|
|
|
static int logicvc_drm_gem_dma_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_dma_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_DMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(logicvc_drm_gem_dma_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 = NULL;
|
|
struct resource res;
|
|
void __iomem *base;
|
|
unsigned int preferred_bpp;
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
switch (drm_dev->mode_config.preferred_depth) {
|
|
case 16:
|
|
preferred_bpp = 16;
|
|
break;
|
|
case 24:
|
|
case 32:
|
|
default:
|
|
preferred_bpp = 32;
|
|
break;
|
|
}
|
|
drm_fbdev_dma_setup(drm_dev, preferred_bpp);
|
|
|
|
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 void 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);
|
|
}
|
|
|
|
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_new = 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");
|