mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-29 01:04:14 -04:00
This is a from-scratch driver targeting Verisilicon DC-series display controllers, which feature self-identification functionality like their GC-series GPUs. Only DC8200 is being supported now, and only the main framebuffer is set up (as the DRM primary plane). Support for more DC models and more features is my further targets. As the display controller is delivered to SoC vendors as a whole part, this driver does not use component framework and extra bridges inside a SoC is expected to be implemented as dedicated bridges (this driver properly supports bridge chaining). Signed-off-by: Icenowy Zheng <uwu@icenowy.me> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn> Tested-by: Han Gao <gaohan@iscas.ac.cn> Tested-by: Michal Wilczynski <m.wilczynski@samsung.com> Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de> Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de> Link: https://patch.msgid.link/20260129023922.1527729-4-zhengxingda@iscas.ac.cn
183 lines
4.2 KiB
C
183 lines
4.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
|
|
*/
|
|
|
|
#include <linux/aperture.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/console.h>
|
|
|
|
#include <drm/clients/drm_client_setup.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_dumb_buffers.h>
|
|
#include <drm/drm_fbdev_dma.h>
|
|
#include <drm/drm_gem_dma_helper.h>
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
|
#include <drm/drm_of.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
#include <drm/drm_vblank.h>
|
|
|
|
#include "vs_bridge.h"
|
|
#include "vs_crtc.h"
|
|
#include "vs_dc.h"
|
|
#include "vs_dc_top_regs.h"
|
|
#include "vs_drm.h"
|
|
|
|
#define DRIVER_NAME "verisilicon"
|
|
#define DRIVER_DESC "Verisilicon DC-series display controller driver"
|
|
#define DRIVER_MAJOR 1
|
|
#define DRIVER_MINOR 0
|
|
|
|
static int vs_gem_dumb_create(struct drm_file *file_priv,
|
|
struct drm_device *drm,
|
|
struct drm_mode_create_dumb *args)
|
|
{
|
|
int ret;
|
|
|
|
/* The hardware wants 128B-aligned pitches for linear buffers. */
|
|
ret = drm_mode_size_dumb(drm, args, 128, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
|
|
}
|
|
|
|
DEFINE_DRM_GEM_FOPS(vs_drm_driver_fops);
|
|
|
|
static const struct drm_driver vs_drm_driver = {
|
|
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
|
|
.fops = &vs_drm_driver_fops,
|
|
.name = DRIVER_NAME,
|
|
.desc = DRIVER_DESC,
|
|
.major = DRIVER_MAJOR,
|
|
.minor = DRIVER_MINOR,
|
|
|
|
/* GEM Operations */
|
|
DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vs_gem_dumb_create),
|
|
DRM_FBDEV_DMA_DRIVER_OPS,
|
|
};
|
|
|
|
static const struct drm_mode_config_funcs vs_mode_config_funcs = {
|
|
.fb_create = drm_gem_fb_create,
|
|
.atomic_check = drm_atomic_helper_check,
|
|
.atomic_commit = drm_atomic_helper_commit,
|
|
};
|
|
|
|
static struct drm_mode_config_helper_funcs vs_mode_config_helper_funcs = {
|
|
.atomic_commit_tail = drm_atomic_helper_commit_tail,
|
|
};
|
|
|
|
static void vs_mode_config_init(struct drm_device *drm)
|
|
{
|
|
drm->mode_config.min_width = 0;
|
|
drm->mode_config.min_height = 0;
|
|
drm->mode_config.max_width = 8192;
|
|
drm->mode_config.max_height = 8192;
|
|
drm->mode_config.funcs = &vs_mode_config_funcs;
|
|
drm->mode_config.helper_private = &vs_mode_config_helper_funcs;
|
|
}
|
|
|
|
int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct vs_drm_dev *vdrm;
|
|
struct drm_device *drm;
|
|
struct vs_crtc *crtc;
|
|
struct vs_bridge *bridge;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
vdrm = devm_drm_dev_alloc(dev, &vs_drm_driver, struct vs_drm_dev, base);
|
|
if (IS_ERR(vdrm))
|
|
return PTR_ERR(vdrm);
|
|
|
|
drm = &vdrm->base;
|
|
vdrm->dc = dc;
|
|
dc->drm_dev = vdrm;
|
|
|
|
ret = drmm_mode_config_init(drm);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Remove early framebuffers (ie. simple-framebuffer) */
|
|
ret = aperture_remove_all_conflicting_devices(DRIVER_NAME);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < dc->identity.display_count; i++) {
|
|
crtc = vs_crtc_init(drm, dc, i);
|
|
if (IS_ERR(crtc))
|
|
return PTR_ERR(crtc);
|
|
|
|
bridge = vs_bridge_init(drm, crtc);
|
|
if (IS_ERR(bridge))
|
|
return PTR_ERR(bridge);
|
|
|
|
vdrm->crtcs[i] = crtc;
|
|
}
|
|
|
|
ret = drm_vblank_init(drm, dc->identity.display_count);
|
|
if (ret)
|
|
return ret;
|
|
|
|
vs_mode_config_init(drm);
|
|
|
|
/* Enable connectors polling */
|
|
drm_kms_helper_poll_init(drm);
|
|
|
|
drm_mode_config_reset(drm);
|
|
|
|
ret = drm_dev_register(drm, 0);
|
|
if (ret)
|
|
goto err_fini_poll;
|
|
|
|
drm_client_setup(drm, NULL);
|
|
|
|
return 0;
|
|
|
|
err_fini_poll:
|
|
drm_kms_helper_poll_fini(drm);
|
|
return ret;
|
|
}
|
|
|
|
void vs_drm_finalize(struct vs_dc *dc)
|
|
{
|
|
struct vs_drm_dev *vdrm = dc->drm_dev;
|
|
struct drm_device *drm = &vdrm->base;
|
|
|
|
drm_dev_unregister(drm);
|
|
drm_kms_helper_poll_fini(drm);
|
|
drm_atomic_helper_shutdown(drm);
|
|
dc->drm_dev = NULL;
|
|
}
|
|
|
|
void vs_drm_shutdown_handler(struct vs_dc *dc)
|
|
{
|
|
struct vs_drm_dev *vdrm = dc->drm_dev;
|
|
|
|
drm_atomic_helper_shutdown(&vdrm->base);
|
|
}
|
|
|
|
void vs_drm_handle_irq(struct vs_dc *dc, u32 irqs)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < dc->identity.display_count; i++) {
|
|
if (irqs & VSDC_TOP_IRQ_VSYNC(i)) {
|
|
irqs &= ~VSDC_TOP_IRQ_VSYNC(i);
|
|
if (dc->drm_dev->crtcs[i])
|
|
drm_crtc_handle_vblank(&dc->drm_dev->crtcs[i]->base);
|
|
}
|
|
}
|
|
|
|
if (irqs)
|
|
drm_warn_once(&dc->drm_dev->base,
|
|
"Unknown Verisilicon DC interrupt 0x%x fired!\n",
|
|
irqs);
|
|
}
|