当前位置: 首页 > article >正文

openharmony系统移植之显示驱动框架从framebuffer升级为drm(linux-5.10)

文章目录

  • 1. drm驱动
    • 1.1 drm平台驱动
      • 1.1.1 从裸机角度思考
        • 1.1.1.1 exynos4412 LCD控制器
        • 1.1.1.2 LCDC控制器和MIPI控制器
      • 1.1.2 drm驱动实现
    • 1.2 panel驱动
    • 1.3 dts配置
  • 2. drm驱动应用测试
    • 2.1 modetest输出彩条
    • 2.2 lvgl

1. drm驱动

openharmony系统要求,linux内核为5.10,显示框架为drm方式。目前手上的设备芯片平台为linux-3.10, 显示为framebuffer机制。前期经过痛苦的折腾已经将芯片平台最小系统移植到linux-5.10。对于drm显示框架,也是刚刚接触,drm显示比较复杂,参考了大量博客文章,如DRM(Direct Rendering Manager)学习简介-CSDN博客, 以下如有问题,欢迎批评指正。

1.1 drm平台驱动

1.1.1 从裸机角度思考

1.1.1.1 exynos4412 LCD控制器

drm框架虽然复杂,但从最底层角度考虑,还是要回归到寄存器操作。先回忆一下8年前写的三星exynos4412的裸机显示驱动,代码在bare_gobang_v1.8.4/drv/lcd.c · lisongze/bare_gobang - 码云 - 开源中国,主要针对RGB显示屏有以下操作。

  • 根据显示屏的实际porch时序(x,y, hbp,hfp,hsync, vbp,hfp,hsync,pixclock)配置lcdc控制器

  • 窗口(图层)初始化

    • 指定窗口色彩格式,起始位置、结束位置等
    • 设置窗口起始地址寄存器,将内存规划的物理地址填入该寄存器。物理地址即为framebuffer
  • 设置LCD的时钟

  • 使能LCD控制器

  • 设置屏幕背光

  • 后续对此framebuffer地址进行数据更新,则显示屏对应更新输出图像

1.1.1.2 LCDC控制器和MIPI控制器

目前手上的设备芯片平台显示部分有lcdc控制器和mipi控制器。LCDC 控制器与 MIPI 之间通过 EDPI
接口相连接。MIPI 接口的数据传输通过 DMA 来完成。图像数据由显存(一般为片外的 RAM)读
入 DMA,并由 DMA 写入 LCDC 模块内部的 FIFO,经过 EDPI 接口输入到 MIPI 控制器和
DSI PHY,最后由 MIPI 接口输出到管脚。操作流程大致如下:

  • 显示控制器所需要的供电,时钟使能

  • lcdc初始化

    • 配置主窗口大小,确定输出格式,设置刷新周期等寄存器操作

    • lcdc dma控制和dma访问间隔寄存器(DMA 发起读操
      作的时间间隔)配置

    • 选择显示图层,图层参数初始化配置,包括显示宽度,高度,色彩格式,起始地址(framebuff)

    • 根据显示屏的实际porch时序(x,y, hbp,hfp,hsync, vbp,hfp,hsync,pixclock)配置lcdc相关控制器

    • mipi接口初始化

      • DSI phy初始化,包括配置mipi的lane个数,高速时钟和低速时钟
      • MIPI配置,包括上电,模式(video/command,Burst type),_POL 极性,配置传输时序参数相关寄存器(hbp,hfp,hsync, vbp,hfp,hsync,需要和 LCDC 配置的参数相匹配)等
      • 显示屏上电,复位,背光设置,初始化参数相关配置下发。
    • 使能lcdc控制控制器

    • 对起始地址(framebuff)进行数据更新,如写入开机logo。

    • 图层使能,图层dma使能。

    • 后续更新起始地址(framebuff)数据,则显示屏对应更新输出图像

1.1.2 drm驱动实现

裸机寄存器角度熟悉后,我们只需要基于drm框架来实现驱动,以及在合适的函数中加上寄存器操作。那么实现drm驱动之前,在内核中找一个最简单的drm驱动参考,常见的rockchip平台,显示涉及lcdc,mipi,hdmi,rgb等,代码比较复杂且代码量比较大,不容易学习上手。stm32平台drm驱动分析后,是一个很好的参考。stm32f429 只有ltdc控制器,stm32f469有ltdc和mipi控制器。其中mipi控制器为新思科技(synopsys)的ip核。

stm平台drm驱动实现代码路径如下。

drivers/gpu/drm/stm:
├── drv.c
├── dw_mipi_dsi-stm.c
├── Kconfig
├── ltdc.c
├── ltdc.h
└── Makefile

手上设备的芯片平台,显示部分为lcdc控制器+mipi控制器(synosys ip),和stm32f469比较接近,因此代码参考stm drm驱动实现。代码如下。其中芯片平台命名隐藏,替换为virtsoc,部分寄存器操作删除,保留注释说明,便于关注drm框架实现。

drivers/gpu/drm/virtsoc
├── dw-mipi-dsi-virtsoc.c
├── Kconfig
├── Makefile
├── virtsoc_drm_crtc.c
├── virtsoc_drm_crtc.h
└── virtsoc_drm_drv.c

vitrtsoc_drm_crtc.c 实现如下:

#include <linux/component.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "virtsoc_drm_crtc.h"


#define VIRTSOC_DRM_MAX_FB_WIDTH		8192
#define VIRTSOC_DRM_MAX_FB_HEIGHT		8192

static const struct drm_mode_config_funcs virtsoc_mode_config_funcs = {
	.fb_create = drm_gem_fb_create,
	.atomic_check = drm_atomic_helper_check,
	.atomic_commit = drm_atomic_helper_commit,
};

DEFINE_DRM_GEM_CMA_FOPS(drv_driver_fops);

static struct drm_driver virtsoc_drm_driver = {
	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
	.name = "virtsoc",
	.desc = "virtual SoC DRM",
	.date = "20250101",
	.major = 1,
	.minor = 0,
	.patchlevel = 0,
	.fops = &drv_driver_fops,
	DRM_GEM_CMA_DRIVER_OPS,/* 需要重点关注 */
};

static void virtsoc_drm_mode_config_init(struct drm_device *dev)
{
	dev->mode_config.min_width = 0;
	dev->mode_config.min_height = 0;
	dev->mode_config.max_width = VIRTSOC_DRM_MAX_FB_WIDTH;
	dev->mode_config.max_height = VIRTSOC_DRM_MAX_FB_HEIGHT;
	dev->mode_config.funcs = &virtsoc_mode_config_funcs;
}

static int virtsoc_drm_drv_load(struct drm_device *ddev)
{
	struct platform_device *pdev = to_platform_device(ddev->dev);
	struct lcdc_device *ldev;
	int ret;

	DRM_DEBUG("%s\n", __func__);

	ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL);
	if (!ldev)
		return -ENOMEM;

	ddev->dev_private = (void *)ldev;

	ret = drmm_mode_config_init(ddev);
	if (ret)
		return ret;

	virtsoc_drm_mode_config_init(ddev);

	ret = virtsoc_drm_lcdc_load(ddev);
	if (ret)
		return ret;

	drm_mode_config_reset(ddev);

	drm_kms_helper_poll_init(ddev);

	platform_set_drvdata(pdev, ddev);

	return 0;
}

static int virtsoc_drm_platform_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct drm_device *ddev;
	int ret;

	DRM_DEBUG("%s\n", __func__);

	ddev = drm_dev_alloc(&virtsoc_drm_driver, dev);
	if (IS_ERR(ddev))
		return PTR_ERR(ddev);

	ret = virtsoc_drm_drv_load(ddev);
	if (ret)
		goto err_put;

	ret = drm_dev_register(ddev, 0);
	if (ret)
		goto err_put;

	return 0;

err_put:
	drm_dev_put(ddev);

	return ret;
}

static int virtsoc_drm_platform_remove(struct platform_device *pdev)
{
	struct drm_device *ddev = platform_get_drvdata(pdev);

	DRM_DEBUG("%s\n", __func__);

	drm_dev_unregister(ddev);
	drm_dev_put(ddev);

	return 0;
}

static const struct of_device_id drv_dt_ids[] = {
	{ .compatible = "virtual,virtsoc-lcdc"},
	{ /* end node */ },
};
MODULE_DEVICE_TABLE(of, drv_dt_ids);

static struct platform_driver virtsoc_drm_platform_driver = {
	.probe = virtsoc_drm_platform_probe,
	.remove = virtsoc_drm_platform_remove,
	.driver = {
		.name = "virtsoc-display",
		.of_match_table = drv_dt_ids,
	},
};

module_platform_driver(virtsoc_drm_platform_driver);

MODULE_AUTHOR("Songze Lee <songze_lee@163.com>");
MODULE_DESCRIPTION("virtual DRM driver");
MODULE_LICENSE("GPL v2");


virtsoc_drm_crtc.c 实现如下,TODO位置需要根据实际芯片平台实现即可。

#include <linux/clk.h>
#include <linux/component.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_graph.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>

#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_device.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>

#include <video/videomode.h>
#include "virtsoc_drm_crtc.h"

#define CLUT_SIZE	256

#define NB_CRTC 1
#define CRTC_MASK GENMASK(NB_CRTC - 1, 0)

uint32_t virtsoc_drm_debug_mask = 0xff;
module_param_named(virtsoc_debug, virtsoc_drm_debug_mask, uint, 0644);

static const uint32_t virtsoc_graphics_formats[] = {
	DRM_FORMAT_ARGB8888,
	DRM_FORMAT_XRGB8888,
	DRM_FORMAT_ABGR8888,
	DRM_FORMAT_RGB888,
	DRM_FORMAT_RGB565,
};

static inline struct lcdc_device *crtc_to_lcdc(struct drm_crtc *crtc)
{
	return (struct lcdc_device *)crtc->dev->dev_private;
}

static inline struct lcdc_device *plane_to_lcdc(struct drm_plane *plane)
{
	return (struct lcdc_device *)plane->dev->dev_private;
}

static inline struct lcdc_device *encoder_to_lcdc(struct drm_encoder *enc)
{
	return (struct lcdc_device *)enc->dev->dev_private;
}

static inline u32 reg_read(void __iomem *base, u32 reg)
{
	return readl_relaxed(base + reg);
}

static inline void reg_write(void __iomem *base, u32 reg, u32 val)
{
	writel_relaxed(val, base + reg);
}

static void virtsoc_lcdc_hw_init(struct lcdc_device *ldev)
{

	/*TODO such as */

	/* 根据芯片平台实际情况配置,如以下配置参考 */
	/* 显示控制器上电 */

	/* 关闭lcdc控制器所有中断 */

	/* 设置输出格式,DMA控制,刷新周期等 */

	/* 使能lcdc mipi接口等 */
}

static irqreturn_t lcdc_irq_handler(int irq, void *data)
{
	struct drm_device *ddev = data;
	struct lcdc_device *ldev = ddev->dev_private;
	struct drm_crtc *crtc = drm_crtc_from_index(ddev, 0);
	u32 status;

	/* Get interrupt status. */
	status = lcdc_int_status(ldev);
	
	/* Clear the interrupt. */
	lcdc_int_clear(ldev, status);

	//DRM_DEBUG_VBL("LCDC IRQ: status=0x%X\n", status);

	/* vblank irq */
	if (status & BIT(LCDC_FTE_INTR)) {
		drm_crtc_handle_vblank(crtc);
	}

	return IRQ_HANDLED;
}

static int virtsoc_request_irq(struct platform_device *pdev, void *data)
{

	int irq;
	int ret;
	struct device *dev = &pdev->dev;

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		DRM_ERROR("failed to get irq\n");
		return -ENODEV;
	}

	/* vblank irq init */
	ret = devm_request_irq(dev, irq, lcdc_irq_handler,
			       IRQF_SHARED, "lcdc irq", data);
	if (ret)
		return -EIO;

	return ret;
}

/*
 * DRM_CRTC
 */

static void virtsoc_drm_lcdc_crtc_atomic_enable(struct drm_crtc *crtc,
				    struct drm_crtc_state *old_state)
{

	struct lcdc_device *ldev = crtc_to_lcdc(crtc);
	struct drm_device *ddev = crtc->dev;

	VIRTSOC_DRM_FUNC_ENTER();

	pm_runtime_get_sync(ddev->dev);


	/* 使能LCDC控制器 */
	/* TODO */

	drm_crtc_vblank_on(crtc);
	VIRTSOC_DRM_FUNC_EXIT();
}

static void virtsoc_drm_lcdc_crtc_atomic_disable(struct drm_crtc *crtc,
				     struct drm_crtc_state *old_state)
{
	struct lcdc_device *ldev = crtc_to_lcdc(crtc);
	struct drm_device *ddev = crtc->dev;

	VIRTSOC_DRM_FUNC_ENTER();

	drm_crtc_vblank_off(crtc);

	/* disable LCDC控制器 */
	/* TODO */

	pm_runtime_put_sync(ddev->dev);

	VIRTSOC_DRM_FUNC_EXIT();
}

static bool virtsoc_drm_lcdc_crtc_mode_fixup(struct drm_crtc *crtc,
				 const struct drm_display_mode *mode,
				 struct drm_display_mode *adjusted_mode)
{
	struct lcdc_device *ldev = crtc_to_lcdc(crtc);
	int rate = mode->clock * 1000;

	VIRTSOC_DRM_FUNC_ENTER();

	/* 配置像素时钟 */
	if (clk_set_rate(ldev->pixel_clk, rate) < 0) {
		DRM_ERROR("Cannot set rate (%dHz) for pixel clk\n", rate);
		return false;
	}

	adjusted_mode->clock = clk_get_rate(ldev->pixel_clk) / 1000;

	DRM_DEBUG_DRIVER("requested clock %dkHz, adjusted clock %dkHz\n",
			 mode->clock, adjusted_mode->clock);

	VIRTSOC_DRM_FUNC_EXIT();

	return true;
}


static void virtsoc_drm_lcdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
	struct lcdc_device *ldev = crtc_to_lcdc(crtc);
	struct drm_display_mode *mode = &crtc->state->mode;
	struct videomode vm;

	VIRTSOC_DRM_FUNC_ENTER();

	drm_display_mode_to_videomode(mode, &vm);

	DRM_DEBUG_DRIVER("Video mode: %dx%d", vm.hactive, vm.vactive);
	DRM_DEBUG_DRIVER(" hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n",
			vm.hfront_porch, vm.hback_porch, vm.hsync_len,
			vm.vfront_porch, vm.vback_porch, vm.vsync_len);

	/* 配置LCDC控制器porch时序参数 */
	/* TODO */

	VIRTSOC_DRM_FUNC_EXIT();

}

static void virtsoc_drm_lcdc_crtc_atomic_begin(struct drm_crtc *crtc,
			     struct drm_crtc_state *old_crtc_state)
{
	VIRTSOC_DRM_FUNC_ENTER();
	VIRTSOC_DRM_FUNC_EXIT();
}

static void virtsoc_drm_lcdc_crtc_atomic_flush(struct drm_crtc *crtc,
				   struct drm_crtc_state *old_crtc_state)
{
	struct drm_device *ddev = crtc->dev;
	struct drm_pending_vblank_event *event = crtc->state->event;

	VIRTSOC_DRM_FUNC_ENTER();

	if (event) {
		crtc->state->event = NULL;

		spin_lock_irq(&ddev->event_lock);
		if (drm_crtc_vblank_get(crtc) == 0)
			drm_crtc_arm_vblank_event(crtc, event);
		else
			drm_crtc_send_vblank_event(crtc, event);
		spin_unlock_irq(&ddev->event_lock);
	}

	VIRTSOC_DRM_FUNC_EXIT();

}

static const struct drm_crtc_helper_funcs virtsoc_drm_lcdc_crtc_helper_funcs = {
	.mode_fixup = virtsoc_drm_lcdc_crtc_mode_fixup,
	.mode_set_nofb = virtsoc_drm_lcdc_crtc_mode_set_nofb,
	.atomic_enable = virtsoc_drm_lcdc_crtc_atomic_enable,
	.atomic_disable = virtsoc_drm_lcdc_crtc_atomic_disable,
	.atomic_begin = virtsoc_drm_lcdc_crtc_atomic_begin,
	.atomic_flush = virtsoc_drm_lcdc_crtc_atomic_flush,
};

static int virtsoc_drm_lcdc_crtc_enable_vblank(struct drm_crtc *crtc)
{
	struct lcdc_device *ldev = crtc_to_lcdc(crtc);

	VIRTSOC_DRM_FUNC_ENTER();

	/* Enable IRQ 配置帧传输结束中断 */
	/* TODO */

	VIRTSOC_DRM_FUNC_EXIT();
	return 0;
}

static void virtsoc_drm_lcdc_crtc_disable_vblank(struct drm_crtc *crtc)
{
	struct lcdc_device *ldev = crtc_to_lcdc(crtc);

	VIRTSOC_DRM_FUNC_ENTER();

	/* disable IRQ 配置帧传输结束中断关闭 */
	/* TODO */

	VIRTSOC_DRM_FUNC_EXIT();
}

static const struct drm_crtc_funcs virtsoc_drm_lcdc_crtc_funcs = {
	.set_config = drm_atomic_helper_set_config,
	.page_flip = drm_atomic_helper_page_flip,
	.destroy = drm_crtc_cleanup,
	.reset = drm_atomic_helper_crtc_reset,
	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
	.enable_vblank = virtsoc_drm_lcdc_crtc_enable_vblank,
	.disable_vblank = virtsoc_drm_lcdc_crtc_disable_vblank,
};

/*
 * DRM_PLANE
 */

static int virtsoc_drm_lcdc_plane_atomic_check(struct drm_plane *plane,
				   struct drm_plane_state *state)
{
	VIRTSOC_DRM_FUNC_ENTER();
	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static void virtsoc_format_translate(u32 drm_fmt, u16 *format)
{
	switch (drm_fmt) {
	case DRM_FORMAT_ARGB8888:
	case DRM_FORMAT_XRGB8888:
		*format = LCDC_LAYER_INPUT_FORMAT_ARGB8888;
		break;
	case DRM_FORMAT_ABGR8888:
		*format = LCDC_LAYER_INPUT_FORMAT_ABGR8888;
		break;
	case DRM_FORMAT_RGB888:
		*format = LCDC_LAYER_INPUT_FORMAT_RGB888;
		break;
	case DRM_FORMAT_RGB565:
		*format = LCDC_LAYER_INPUT_FORMAT_RGB565;
		break;
	default:
		DRM_INFO("%s fmt:0x%x, default\n", __func__, drm_fmt);
		*format = LCDC_LAYER_INPUT_FORMAT_ARGB8888;
		break;
	}
	return;
}


static void virtsoc_drm_lcdc_plane_atomic_update(struct drm_plane *plane,
				     struct drm_plane_state *oldstate)
{

	struct lcdc_device *ldev = plane_to_lcdc(plane);
	struct drm_plane_state *state = plane->state;
	struct drm_framebuffer *fb = state->fb;

	u32 src_x, src_y, src_w, src_h;
	//struct drm_gem_object *gem = NULL;
	//struct drm_gem_cma_object *cma_gem = NULL;
	dma_addr_t paddr;
	u16 format = 0;

	VIRTSOC_DRM_FUNC_ENTER();

	if (!state->crtc || !fb) {
		DRM_DEBUG_DRIVER("fb or crtc NULL");
		return;
	}

	/* convert src_ from 16:16 format */
	src_x = state->src_x >> 16;
	src_y = state->src_y >> 16;
	src_w = state->src_w >> 16;
	src_h = state->src_h >> 16;

	virtsoc_drm_debug("plane:%d fb:%d (%dx%d)@(%d,%d) -> (%dx%d)@(%d,%d)\n",
			 plane->base.id, fb->base.id,
			 src_w, src_h, src_x, src_y,
			 state->crtc_w, state->crtc_h,
			 state->crtc_x, state->crtc_y);

	/* format */
	//DRM_INFO("%s format:0x%x\n", __func__, fb->format->format);
    /*将drm格式转化为芯片平台对应格式的数值,便于操作寄存器,TODO*/
	virtsoc_format_translate(fb->format->format, &format);

	/* buffer address */
#if 1
	paddr = (u32)drm_fb_cma_get_gem_addr(fb, state, 0);/*获取framebuff 内存物理地址,重点关注*/
#else
	/* for cma gem only. */
	gem = drm_gem_fb_get_obj(fb, 0);
	cma_gem = to_drm_gem_cma_obj(gem);
	/* no memory */
	if (IS_ERR_OR_NULL(cma_gem)) {
		DRM_ERROR("%s cma no memory\n", __func__);
		return;
	}

	paddr = cma_gem->paddr;
#endif
	/* 根据 crtc_w crtc_h format 初始化 lcdc控制器图层参数并使能图层 */
	/* TODO */

	VIRTSOC_DRM_FUNC_EXIT();

}

static const struct drm_plane_funcs virtsoc_drm_lcdc_plane_funcs = {
	.update_plane = drm_atomic_helper_update_plane,
	.disable_plane = drm_atomic_helper_disable_plane,
	.destroy = drm_plane_cleanup,
	.reset = drm_atomic_helper_plane_reset,
	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};

static const struct drm_plane_helper_funcs virtsoc_drm_lcdc_plane_helper_funcs = {
	.atomic_check = virtsoc_drm_lcdc_plane_atomic_check,
	.atomic_update = virtsoc_drm_lcdc_plane_atomic_update,
};

static struct drm_plane *virtsoc_drm_lcdc_plane_create(struct drm_device *ddev,
					   enum drm_plane_type type)
{
	unsigned long possible_crtcs = CRTC_MASK;
	struct device *dev = ddev->dev;
	struct drm_plane *plane;
	int ret;

	VIRTSOC_DRM_FUNC_ENTER();

	plane = devm_kzalloc(dev, sizeof(*plane), GFP_KERNEL);
	if (!plane)
		return NULL;

	ret = drm_universal_plane_init(ddev, plane, possible_crtcs,
		       &virtsoc_drm_lcdc_plane_funcs, virtsoc_graphics_formats,
		       ARRAY_SIZE(virtsoc_graphics_formats),
		       NULL, type, NULL);
	if (ret < 0)
		return NULL;

	drm_plane_helper_add(plane, &virtsoc_drm_lcdc_plane_helper_funcs);

	DRM_DEBUG_DRIVER("plane:%d created\n", plane->base.id);
	VIRTSOC_DRM_FUNC_EXIT();

	return plane;
}

static void virtsoc_drm_lcdc_plane_destroy_all(struct drm_device *ddev)
{
	struct drm_plane *plane, *plane_temp;

	VIRTSOC_DRM_FUNC_ENTER();

	list_for_each_entry_safe(plane, plane_temp,
				 &ddev->mode_config.plane_list, head)
		drm_plane_cleanup(plane);
	VIRTSOC_DRM_FUNC_EXIT();

}

static int virtsoc_drm_lcdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
{
	struct drm_plane *primary;
	int ret;

	VIRTSOC_DRM_FUNC_ENTER();

	/* 这里只创建PRIMARY */
	primary = virtsoc_drm_lcdc_plane_create(ddev, DRM_PLANE_TYPE_PRIMARY);
	if (!primary) {
		DRM_ERROR("Can not create primary plane\n");
		return -EINVAL;
	}

	ret = drm_crtc_init_with_planes(ddev, crtc, primary, NULL,
					&virtsoc_drm_lcdc_crtc_funcs, NULL);
	if (ret) {
		DRM_ERROR("Can not initialize CRTC\n");
		goto cleanup;
	}

	drm_crtc_helper_add(crtc, &virtsoc_drm_lcdc_crtc_helper_funcs);

	drm_mode_crtc_set_gamma_size(crtc, CLUT_SIZE);
	//drm_crtc_enable_color_mgmt(crtc, 0, false, CLUT_SIZE);

	DRM_DEBUG_DRIVER("CRTC:%d created\n", crtc->base.id);

	VIRTSOC_DRM_FUNC_EXIT();

	return 0;

cleanup:
	virtsoc_drm_lcdc_plane_destroy_all(ddev);
	return ret;
}



/*
 * DRM_ENCODER
 */

static const struct drm_encoder_funcs virtsoc_drm_lcdc_encoder_funcs = {
	.destroy = drm_encoder_cleanup,
};

static void virtsoc_drm_lcdc_encoder_disable(struct drm_encoder *encoder)
{
	VIRTSOC_DRM_FUNC_ENTER();
	VIRTSOC_DRM_FUNC_EXIT();
}

static void virtsoc_drm_lcdc_encoder_enable(struct drm_encoder *encoder)
{
	VIRTSOC_DRM_FUNC_ENTER();
	VIRTSOC_DRM_FUNC_EXIT();
}

static void virtsoc_drm_lcdc_encoder_mode_set(struct drm_encoder *encoder,
				  struct drm_display_mode *mode,
				  struct drm_display_mode *adjusted_mode)
{
	VIRTSOC_DRM_FUNC_ENTER();
	VIRTSOC_DRM_FUNC_EXIT();
}

static const struct drm_encoder_helper_funcs virtsoc_drm_lcdc_encoder_helper_funcs = {
	.disable = virtsoc_drm_lcdc_encoder_disable,
	.enable = virtsoc_drm_lcdc_encoder_enable,
	.mode_set = virtsoc_drm_lcdc_encoder_mode_set,
};

static int virtsoc_drm_lcdc_encoder_init(struct drm_device *ddev, struct drm_bridge *bridge)
{
	struct drm_encoder *encoder;
	int ret;
	VIRTSOC_DRM_FUNC_ENTER();

	encoder = devm_kzalloc(ddev->dev, sizeof(*encoder), GFP_KERNEL);
	if (!encoder)
		return -ENOMEM;

	encoder->possible_crtcs = CRTC_MASK;
	encoder->possible_clones = 0;	/* No cloning support */

	drm_encoder_init(ddev, encoder, &virtsoc_drm_lcdc_encoder_funcs,
			 DRM_MODE_ENCODER_DPI, NULL);

	drm_encoder_helper_add(encoder, &virtsoc_drm_lcdc_encoder_helper_funcs);

	ret = drm_bridge_attach(encoder, bridge, NULL, 0);
	if (ret) {
		drm_encoder_cleanup(encoder);
		return -EINVAL;
	}

	DRM_DEBUG_DRIVER("Bridge encoder:%d created\n", encoder->base.id);
	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

int virtsoc_drm_lcdc_load(struct drm_device *ddev)
{
	struct platform_device *pdev = to_platform_device(ddev->dev);
	struct lcdc_device *ldev = ddev->dev_private;
	struct device *dev = ddev->dev;
	struct device_node *np = dev->of_node;
	struct drm_bridge *bridge;
	struct drm_panel *panel;
	struct drm_crtc *crtc;
	struct resource *res;
	int i, nb_endpoints;
	int ret = -ENODEV;

	DRM_DEBUG_DRIVER("\n");
	VIRTSOC_DRM_FUNC_ENTER();

	/* Get number of endpoints */
	nb_endpoints = of_graph_get_endpoint_count(np);
	if (!nb_endpoints)
		return -ENODEV;

	/* pixel_clk是必须配置的 */
	ldev->pixel_clk = devm_clk_get(dev, "lcdc_mclk");
	if (IS_ERR(ldev->pixel_clk)) {
		if (PTR_ERR(ldev->pixel_clk) != -EPROBE_DEFER)
			DRM_ERROR("Unable to get lcd clock\n");
		return PTR_ERR(ldev->pixel_clk);
	}

	if (clk_prepare_enable(ldev->pixel_clk)) {
		DRM_ERROR("Unable to prepare pixel clock\n");
		return -ENODEV;
	}

	/* axi_clk 是芯片平台私有需要配置的 根据芯片实际情况选择修改 */
	ldev->axi_clk = devm_clk_get(dev, "lcdc_axi_clk");
	if (IS_ERR(ldev->axi_clk)) {
		if (PTR_ERR(ldev->axi_clk) != -EPROBE_DEFER)
			DRM_ERROR("Unable to get lcd clock\n");
		return PTR_ERR(ldev->axi_clk);
	}

	clk_set_rate(ldev->axi_clk, 312000000);

	if (clk_prepare_enable(ldev->axi_clk)) {
		DRM_ERROR("Unable to prepare axi clock\n");
		return -ENODEV;
	}

	/* Get endpoints if any */
	for (i = 0; i < nb_endpoints; i++) {
		VIRTSOC_DRM_FUNC_ENTER();
		ret = drm_of_find_panel_or_bridge(np, 0, i, &panel, &bridge);

		/*
		 * If at least one endpoint is -ENODEV, continue probing,
		 * else if at least one endpoint returned an error
		 * (ie -EPROBE_DEFER) then stop probing.
		 */
		if (ret == -ENODEV)
			continue;
		else if (ret)
			goto err;

		if (bridge) {
			VIRTSOC_DRM_FUNC_ENTER();
			ret = virtsoc_drm_lcdc_encoder_init(ddev, bridge);
			if (ret) {
				DRM_ERROR("init encoder endpoint %d\n", i);
				goto err;
			}
		}
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	ldev->regs = devm_ioremap_resource(dev, res);
	if (IS_ERR(ldev->regs)) {
		DRM_ERROR("Unable to get lcdc registers\n");
		ret = PTR_ERR(ldev->regs);
		goto err;
	}

	virtsoc_lcdc_hw_init(ldev);
	virtsoc_request_irq(pdev, ddev);

	crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
	if (!crtc) {
		DRM_ERROR("Failed to allocate crtc\n");
		ret = -ENOMEM;
		goto err;
	}

	ddev->mode_config.allow_fb_modifiers = true;

	ret = virtsoc_drm_lcdc_crtc_init(ddev, crtc);
	if (ret) {
		DRM_ERROR("Failed to init crtc\n");
		goto err;
	}

	ret = drm_vblank_init(ddev, NB_CRTC);
	if (ret) {
		DRM_ERROR("Failed calling drm_vblank_init()\n");
		goto err;
	}

	ddev->irq_enabled = 1;

	pm_runtime_enable(ddev->dev);

	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
err:
	for (i = 0; i < nb_endpoints; i++)
		drm_of_panel_bridge_remove(ddev->dev->of_node, 0, i);

	clk_disable_unprepare(ldev->pixel_clk);

	return ret;
}

void virtsoc_drm_lcdc_unload(struct drm_device *ddev)
{
	struct device *dev = ddev->dev;
	int nb_endpoints, i;

	DRM_DEBUG_DRIVER("\n");
	VIRTSOC_DRM_FUNC_ENTER();

	nb_endpoints = of_graph_get_endpoint_count(dev->of_node);

	for (i = 0; i < nb_endpoints; i++)
		drm_of_panel_bridge_remove(ddev->dev->of_node, 0, i);

	pm_runtime_disable(ddev->dev);
	VIRTSOC_DRM_FUNC_EXIT();
}

MODULE_AUTHOR("Songze Lee <songze_lee@163.com>");
MODULE_DESCRIPTION("virtual soc DRM LCDC driver");
MODULE_LICENSE("GPL v2");

virtsoc_drm_crtc.h实现如下:

#ifndef _LCDC_H_
#define _LCDC_H_

extern uint32_t virtsoc_drm_debug_mask;
#define virtsoc_drm_debug(x...) \
	do { \
		if (virtsoc_drm_debug_mask) \
			DRM_INFO(x); \
	} while (0)


#define VIRTUAL_DRM_FUNC_ENTER() virtsoc_drm_debug("function %s line: %d enter...\n", __FUNCTION__, __LINE__)
#define VIRTUAL_DRM_FUNC_EXIT() virtsoc_drm_debug("function %s line: %d exit...\n", __FUNCTION__, __LINE__)


#define LCDC_MAX_LAYER	2

struct lcdc_device {
	void __iomem *regs;	/* lcdc reg base */
	struct clk *axi_clk;	/* lcd axi clock 芯片平台私有的 */
	struct clk *pixel_clk;	/* lcd pixel clock */
	u32 irq_status;
	struct drm_atomic_state *suspend_state;
};

int virtsoc_drm_lcdc_load(struct drm_device *ddev);
void virtsoc_drm_unload(struct drm_device *ddev);
void virtsoc_drm_lcdc_suspend(struct drm_device *ddev);
int virtsoc_drm_lcdc_resume(struct drm_device *ddev);

#endif

dw-mipi-dsi-virtsoc.c 是对bridge/synopsys/dw-mipi-dsi.c 平台差异化实现。此代码仅仅实现phy_ops和host_ops。mipi控制器更多复杂的实现在dw-mipi-dsi.c,在实际调试过程中,针对手上芯片平台 dw-mipi-dsi.c还有部分bug需要修改。

dw-mipi-dsi-virtsoc.c 实现如下:


#include <linux/clk.h>
#include <linux/iopoll.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>

#include <video/mipi_display.h>

#include <drm/bridge/dw_mipi_dsi.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_print.h>

#define VIRTSOC_DRM_FUNC_ENTER() DRM_INFO("function %s enter...\n", __FUNCTION__)
#define VIRTSOC_DRM_FUNC_EXIT() DRM_INFO("function %s exit...\n", __FUNCTION__)


/* DSI digital registers & bit definitions */
#define DSI_VERSION			0x00
#define VERSION				GENMASK(31, 8)

#define DSI_PHY_TST_CTRL0		0xb4
#define PHY_TESTCLK			BIT(1)
#define PHY_UNTESTCLK			0
#define PHY_TESTCLR			BIT(0)
#define PHY_UNTESTCLR			0

#define DSI_PHY_TST_CTRL1		0xb8
#define PHY_TESTEN			BIT(16)
#define PHY_UNTESTEN			0
#define PHY_TESTDOUT(n)			(((n) & 0xff) << 8)
#define PHY_TESTDIN(n)			(((n) & 0xff) << 0)

#define PHY_TESTCLK_MASK		GENMASK(1, 1)
#define PHY_TESTCLR_MASK		GENMASK(0, 0)
#define PHY_TESTEN_MASK			GENMASK(16, 16)
#define PHY_TESTDOUT_MASK		GENMASK(15, 8)
#define PHY_TESTDIN_MASK		GENMASK(7, 0)


struct dw_mipi_dsi_virtsoc {
	void __iomem *base;
	struct clk *pllref_clk;
	struct dw_mipi_dsi *dsi;
	u32 hw_version;
	unsigned long hs_rate;
	unsigned long lp_rate;
};

static inline void dsi_write(struct dw_mipi_dsi_virtsoc *dsi, u32 reg, u32 val)
{
	writel(val, dsi->base + reg);
}

static inline u32 dsi_read(struct dw_mipi_dsi_virtsoc *dsi, u32 reg)
{
	return readl(dsi->base + reg);
}

static inline void dsi_set(struct dw_mipi_dsi_virtsoc *dsi, u32 reg, u32 mask)
{
	dsi_write(dsi, reg, dsi_read(dsi, reg) | mask);
}

static inline void dsi_clear(struct dw_mipi_dsi_virtsoc *dsi, u32 reg, u32 mask)
{
	dsi_write(dsi, reg, dsi_read(dsi, reg) & ~mask);
}

static inline void dsi_update_bits(struct dw_mipi_dsi_virtsoc *dsi, u32 reg,
				   u32 mask, u32 val)
{
	dsi_write(dsi, reg, (dsi_read(dsi, reg) & ~mask) | val);
}


static int dw_mipi_dsi_phy_init(void *priv_data)
{
	VIRTSOC_DRM_FUNC_ENTER();
	/* 根据芯片实际情况对dsi phy初始化,配置相关寄存器 */
	/*TODO */

	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static void dw_mipi_dsi_phy_power_on(void *priv_data)
{
	VIRTSOC_DRM_FUNC_ENTER();
	/* 根据芯片实际情况对dsi phy初始化,配置相关寄存器 */
	/*TODO */
	VIRTSOC_DRM_FUNC_EXIT();

}

static void dw_mipi_dsi_phy_power_off(void *priv_data)
{
	VIRTSOC_DRM_FUNC_ENTER();
	/* 根据芯片实际情况对dsi phy初始化,配置相关寄存器 */
	/*TODO */
	VIRTSOC_DRM_FUNC_EXIT();
}

static int
dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
			  unsigned long mode_flags, u32 lanes, u32 format,
			  unsigned int *lane_mbps)
{

	struct dw_mipi_dsi_virtsoc *dsi = priv_data;

	VIRTSOC_DRM_FUNC_ENTER();

	pr_info("%s hs_rate:%lu\n", __func__, dsi->hs_rate);

	*lane_mbps = dsi->hs_rate/1000/1000*8; /* 配置高速时钟 */

	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static int dw_mipi_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps,
			   struct dw_mipi_dsi_dphy_timing *timing)
{

	int i;

	VIRTSOC_DRM_FUNC_ENTER();
	/* 根据芯片平台情况,计算timing,并对其赋值 */

	/* TODO */

	/*
	timing->clk_hs2lp = xxx;
	timing->clk_lp2hs = xxx;
	timing->data_hs2lp = xxx;
	timing->data_lp2hs = xxx;
	*/
	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static int dw_mipi_dsi_phy_get_esc_clk_rate(void *priv_data, unsigned int *esc_clk_rate)
{

	struct dw_mipi_dsi_virtsoc *dsi = priv_data;

	VIRTSOC_DRM_FUNC_ENTER();

	pr_info("%s lp_rate:%lu\n", __func__, dsi->lp_rate);

	*esc_clk_rate = dsi->lp_rate/1000/1000; /*mipi低速时钟*/

	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static int dw_mipi_dsi_virtsoc_host_attach(void *priv_data,
					    struct mipi_dsi_device *device)
{
	struct dw_mipi_dsi_virtsoc *dsi = priv_data;

	VIRTSOC_DRM_FUNC_ENTER();

	/* 这里我们仅仅为了拿到panel传递过来的高速时钟和低速时钟*/
	dsi->hs_rate = device->hs_rate;
	dsi->lp_rate = device->lp_rate;

	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static int dw_mipi_dsi_virtsoc_host_detach(void *priv_data,
					    struct mipi_dsi_device *device)
{
	VIRTSOC_DRM_FUNC_ENTER();
	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}


static const struct dw_mipi_dsi_phy_ops dw_mipi_dsi_virtsoc_phy_ops = {
	.init = dw_mipi_dsi_phy_init,
	.power_on = dw_mipi_dsi_phy_power_on,
	.power_off = dw_mipi_dsi_phy_power_off,
	.get_lane_mbps = dw_mipi_dsi_get_lane_mbps,
	.get_timing = dw_mipi_dsi_phy_get_timing,
	.get_esc_clk_rate = dw_mipi_dsi_phy_get_esc_clk_rate,
};


static const struct dw_mipi_dsi_host_ops dw_mipi_dsi_virtsoc_host_ops = {
	.attach = dw_mipi_dsi_virtsoc_host_attach,
	.detach = dw_mipi_dsi_virtsoc_host_detach,
};

static struct dw_mipi_dsi_plat_data dw_mipi_dsi_virtsoc_plat_data = {
	.max_data_lanes = 4, /* 根据芯片平台实际调整lane个数, TODO*/
	.phy_ops = &dw_mipi_dsi_virtsoc_phy_ops,
	.host_ops = &dw_mipi_dsi_virtsoc_host_ops,
};

static const struct of_device_id dw_mipi_dsi_virtsoc_dt_ids[] = {
	{ .compatible = "virtual,virtsoc-dsi", .data = &dw_mipi_dsi_virtsoc_plat_data, },
	{ },
};
MODULE_DEVICE_TABLE(of, dw_mipi_dsi_virtsoc_dt_ids);

static int dw_mipi_dsi_virtsoc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct dw_mipi_dsi_virtsoc *dsi;
	struct resource *res;
	int ret;

	VIRTSOC_DRM_FUNC_ENTER();

	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
	if (!dsi)
		return -ENOMEM;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	dsi->base = devm_ioremap_resource(dev, res);
	if (IS_ERR(dsi->base)) {
		ret = PTR_ERR(dsi->base);
		DRM_ERROR("Unable to get dsi registers %d\n", ret);
		return ret;
	}

	dsi->pllref_clk = devm_clk_get(dev, "ref");
	if (IS_ERR(dsi->pllref_clk)) {
		ret = PTR_ERR(dsi->pllref_clk);
		if (ret != -EPROBE_DEFER)
			DRM_ERROR("Unable to get pll reference clock: %d\n",
				  ret);
	}

	ret = clk_prepare_enable(dsi->pllref_clk);
	if (ret) {
		DRM_ERROR("Failed to enable pllref_clk: %d\n", ret);
	}

	dsi->hw_version = dsi_read(dsi, DSI_VERSION);

	DRM_INFO("DSI version:0x%x\n", dsi->hw_version);

	dw_mipi_dsi_virtsoc_plat_data.base = dsi->base;
	dw_mipi_dsi_virtsoc_plat_data.priv_data = dsi;

	platform_set_drvdata(pdev, dsi);

	dsi->dsi = dw_mipi_dsi_probe(pdev, &dw_mipi_dsi_virtsoc_plat_data);
	if (IS_ERR(dsi->dsi)) {
		ret = PTR_ERR(dsi->dsi);
		DRM_ERROR("Failed to initialize mipi dsi host: %d\n", ret);
	}

	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static int dw_mipi_dsi_virtsoc_remove(struct platform_device *pdev)
{
	struct dw_mipi_dsi_virtsoc *dsi = platform_get_drvdata(pdev);

	VIRTSOC_DRM_FUNC_ENTER();

	dw_mipi_dsi_remove(dsi->dsi);

	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static int __maybe_unused dw_mipi_dsi_virtsoc_suspend(struct device *dev)
{

	VIRTSOC_DRM_FUNC_ENTER();
	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static int __maybe_unused dw_mipi_dsi_virtsoc_resume(struct device *dev)
{

	VIRTSOC_DRM_FUNC_ENTER();
	VIRTSOC_DRM_FUNC_EXIT();

	return 0;
}

static const struct dev_pm_ops dw_mipi_dsi_virtsoc_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(dw_mipi_dsi_virtsoc_suspend,
				dw_mipi_dsi_virtsoc_resume)
};


static struct platform_driver dw_mipi_dsi_virtsoc_driver = {
	.probe		= dw_mipi_dsi_virtsoc_probe,
	.remove		= dw_mipi_dsi_virtsoc_remove,
	.driver		= {
		.of_match_table = dw_mipi_dsi_virtsoc_dt_ids,
		.name	= "virtsoc-display-dsi",
		.pm = &dw_mipi_dsi_virtsoc_pm_ops,
	},
};

module_platform_driver(dw_mipi_dsi_virtsoc_driver);

MODULE_AUTHOR("Songze Lee <songze_lee@163.com>");
MODULE_DESCRIPTION("virtual DW MIPI DSI host controller driver");
MODULE_LICENSE("GPL v2");

dw-mipi-dsi.c bug修复patch如下,当然在最新的高版本官方(kernel.org)kernel中也有提交patch修改。

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
index 376fa6eb46f6..733219bc2e92 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
@@ -637,7 +637,7 @@ static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi)
         * timeout clock division should be computed with the
         * high speed transmission counter timeout and byte lane...
         */
-       dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(10) |
+       dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(0) |
                  TX_ESC_CLK_DIVISION(esc_clk_division));
 }

@@ -673,7 +673,13 @@ static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi,

 static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi)
 {
-       dsi_write(dsi, DSI_PCKHDL_CFG, CRC_RX_EN | ECC_RX_EN | BTA_EN);
+       u32 val = CRC_RX_EN | ECC_RX_EN | BTA_EN | EOTP_TX_EN;
+
+
+       if (dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)
+               val &= ~EOTP_TX_EN;
+
+       dsi_write(dsi, DSI_PCKHDL_CFG, val);
 }

Makefile 如下:

virtsoc-drm-y := \
        virtsoc_drm_drv.o \
        virtsoc_drm_crtc.o

obj-$(CONFIG_DRM_VIRTSOC_DSI) += dw-mipi-dsi-virtsoc.o

obj-$(CONFIG_DRM_VIRTSOC) += virtsoc-drm.o

Kconfig如下:

config DRM_VIRTSOC
        tristate "DRM Support for Virtual SoC Series"
        depends on DRM && (ARCH_COMIP || ARCH_MULTIPLATFORM)
        select DRM_KMS_HELPER
        select DRM_GEM_CMA_HELPER
        select DRM_KMS_CMA_HELPER
        select DRM_PANEL_BRIDGE
        select VIDEOMODE_HELPERS
        select FB_PROVIDE_GET_FB_UNMAPPED_AREA if FB

        help
          Enable support for the on-chip display controller on
          virtual soc.
          To compile this driver as a module, choose M here: the module
          will be called virtsoc-drm.

config DRM_VIRTSOC_DSI
        tristate "Leadcore specific extensions for Synopsys MIPI DSI"
        depends on DRM_VIRTSOC
        select DRM_DW_MIPI_DSI
        help
          Choose this option for MIPI DSI support on virtual SoC.

1.2 panel驱动

panel驱动在drivers/gpu/drm/panel下,我们参考openharmony源码中kernel/linux/patches/linux-5.10/rk3568_patch中panel-simple.c的相关patch和drivers/hdf_core/framework/model/display/driver/hdf_drm_panel.c,rk3568平台plane驱动对屏幕参数进行了很好管理,由dts中传递。这部分可以吸收过来。hdf_drm_panel.c 实现panel驱动框架,以及抽象处理。

panel驱动的c文件命名规则,遵循panel-屏幕厂家-ic名称.c, 以下定义假的屏幕厂家为virtual,ic名称为canary9725,来实现panel-virtual-canary9725.c驱动。代码如下:


#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <video/of_display_timing.h>

#include <video/mipi_display.h>

#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>

#define VIRT_DRM_FUNC_ENTER() pr_info("panel function %s line:%d enter...\n", __FUNCTION__, __LINE__)
#define VIRT_DRM_FUNC_EXIT() pr_info("panel function %s line: %d exit...\n", __FUNCTION__, __LINE__)


struct panel_canary9527 {
	struct drm_panel base;
	struct mipi_dsi_device *dsi;
	bool prepared;
	bool enabled;

	const struct panel_desc *desc;

	struct regulator *lcd_core_supply;
	struct regulator *lcd_io_supply;
	struct gpio_desc *reset_gpio;

	struct drm_display_mode override_mode;

};

static inline struct panel_canary9527 *to_panel_canary9527(struct drm_panel *panel)
{
	return container_of(panel, struct panel_canary9527, base);
}

 struct panel_desc {
	const struct drm_display_mode *modes;
	unsigned int num_modes;
	const struct display_timing *timings;
	unsigned int num_timings;

	unsigned int bpc;

	/**
	* @width: width (in millimeters) of the panel's active display area
	* @height: height (in millimeters) of the panel's active display area
	*/
	struct {
	    unsigned int width;
	    unsigned int height;
	} size;

	/**
	* @prepare: the time (in milliseconds) that it takes for the panel to
	*		 become ready and start receiving video data
	* @enable: the time (in milliseconds) that it takes for the panel to
	*		display the first valid frame after starting to receive
	*		video data
	* @disable: the time (in milliseconds) that it takes for the panel to
	*		 turn the display off (no content is visible)
	* @unprepare: the time (in milliseconds) that it takes for the panel
	*		   to power itself down completely
	* @reset: the time (in milliseconds) that it takes for the panel
	*	       to reset itself completely
	* @init: the time (in milliseconds) that it takes for the panel to
	*	      send init command sequence after reset deassert
	*/
	struct {
	    unsigned int prepare;
	    unsigned int enable;
	    unsigned int disable;
	    unsigned int unprepare;
	    unsigned int reset[3];
	    unsigned int init;
	} delay;

	u32 bus_format;
	u32 bus_flags;

	struct panel_cmd_seq *init_seq;
	struct panel_cmd_seq *exit_seq;
};

struct panel_desc_dsi {
	struct panel_desc desc;

	unsigned long flags;
	enum mipi_dsi_pixel_format format;
	unsigned int lanes;
	unsigned int hs_rate;
	unsigned int lp_rate;
};

struct panel_cmd_header {
	u8 delay;
	u8 data_type;
	u8 payload_length;
} __packed;

struct panel_cmd_desc {
	struct panel_cmd_header header;
	u8 *payload;
};

struct panel_cmd_seq {
	struct panel_cmd_desc *cmds;
	unsigned int cmd_cnt;
};


int panel_canary9527_read_id(struct mipi_dsi_device *dsi)
{
	u8 cmd = 0x04;
	u8 data[6] = {0};
	u8 len = 2;
	int err;

	err = mipi_dsi_dcs_read(dsi, cmd, data, len);
	if (err < 0) {
		pr_err("%s error %d reading dcs seq(%#x)\n", __func__, err, cmd);
	}

	pr_err("%s ID: 0x%2x, 0x%2x\n",  __func__, data[0], data[1]);

	return 0;

}

static int panel_canary9527_xfer_dsi_cmd_seq(struct panel_canary9527 *panel,
					 struct panel_cmd_seq *seq)
{
	struct device *dev = panel->base.dev;
	struct mipi_dsi_device *dsi = panel->dsi;
	unsigned int i;
	int err;

	VIRT_DRM_FUNC_ENTER();

	if (!seq)
		return -EINVAL;

	for (i = 0; i < seq->cmd_cnt; i++) {
		struct panel_cmd_desc *cmd = &seq->cmds[i];

		switch (cmd->header.data_type) {
		case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
		case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
		case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
		case MIPI_DSI_GENERIC_LONG_WRITE:
			err = mipi_dsi_generic_write(dsi, cmd->payload,
						     cmd->header.payload_length);
			break;
		case MIPI_DSI_DCS_SHORT_WRITE:
		case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
		case MIPI_DSI_DCS_LONG_WRITE:
			err = mipi_dsi_dcs_write_buffer(dsi, cmd->payload,
							cmd->header.payload_length);
			break;
		default:
			return -EINVAL;
		}

		if (err < 0)
			dev_err(dev, "failed to write dcs cmd: %d\n", err);

		if (cmd->header.delay)
			msleep(cmd->header.delay);
	}

	VIRT_DRM_FUNC_EXIT();

	return 0;
}

static int panel_canary9527_disable(struct drm_panel *panel)
{
	struct panel_canary9527 *p = to_panel_canary9527(panel);

	VIRT_DRM_FUNC_ENTER();

	if (!p->enabled)
		return 0;

	if (p->desc->delay.disable)
		msleep(p->desc->delay.disable);

	p->enabled = false;

	VIRT_DRM_FUNC_EXIT();

	return 0;
}

static int panel_canary9527_unprepare(struct drm_panel *panel)
{
	struct panel_canary9527 *p = to_panel_canary9527(panel);

	VIRT_DRM_FUNC_ENTER();

	if (!p->prepared)
		return 0;

	regulator_disable(p->lcd_core_supply);
	regulator_disable(p->lcd_io_supply);

	if (p->desc->delay.unprepare)
		msleep(p->desc->delay.unprepare);

	p->prepared = false;

	VIRT_DRM_FUNC_EXIT();

	return 0;
}

static int panel_canary9527_prepare(struct drm_panel *panel)
{
	struct panel_canary9527 *p = to_panel_canary9527(panel);
	int ret;

	VIRT_DRM_FUNC_ENTER();

	if (p->prepared)
		return 0;

	ret = regulator_enable(p->lcd_core_supply);
	if (ret < 0) {
		dev_err(panel->dev, "failed to core supply: %d\n", ret);
		return ret;
	}

	ret = regulator_enable(p->lcd_io_supply);
	if (ret < 0) {
		dev_err(panel->dev, "failed to io supply: %d\n", ret);
		return ret;
	}

	if (p->reset_gpio) {
		gpiod_set_value_cansleep(p->reset_gpio, 1);
		msleep(p->desc->delay.reset[0]);
		gpiod_set_value_cansleep(p->reset_gpio, 0);
		msleep(p->desc->delay.reset[1]);
		gpiod_set_value_cansleep(p->reset_gpio, 1);
		msleep(p->desc->delay.reset[2]);
	}

	panel_canary9527_read_id(p->dsi);//读取id,验证mipi接口 debug使用

	/* 下发屏幕初始化参数 */
	if (p->desc->init_seq)
		if (p->dsi)
			panel_canary9527_xfer_dsi_cmd_seq(p, p->desc->init_seq);

	if (p->desc->delay.init)
		msleep(p->desc->delay.init);

	p->prepared = true;

	VIRT_DRM_FUNC_EXIT();

	return 0;
}

static int panel_canary9527_enable(struct drm_panel *panel)
{
	struct panel_canary9527 *p = to_panel_canary9527(panel);

	VIRT_DRM_FUNC_ENTER();

	if (p->enabled)
		return 0;

	if (p->desc->delay.enable)
		msleep(p->desc->delay.enable);

	p->enabled = true;

	VIRT_DRM_FUNC_EXIT();

	return 0;
}

static int panel_canary9527_get_modes(struct drm_panel *panel,
			      struct drm_connector *connector)
{
	struct panel_canary9527 *p = to_panel_canary9527(panel);

	const struct drm_display_mode *default_mode;
	struct drm_display_mode *mode;

	VIRT_DRM_FUNC_ENTER();
	
	if (panel == NULL) {
		pr_err("%s panel is NULL\n", __func__);
		return 0;
	}

	if (connector == NULL) {
		pr_err("%s connector is NULL\n", __func__);
		return 0;
	}

	default_mode = &p->desc->modes[0];
	mode = drm_mode_duplicate(connector->dev, default_mode);
	if (!mode) {
		dev_err(panel->dev, "failed to add mode %ux%ux@%u",
			default_mode->hdisplay, default_mode->vdisplay,
		drm_mode_vrefresh(mode));
		return -ENOMEM;
	}

	drm_mode_set_name(mode);

	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
	connector->display_info.width_mm = mode->width_mm;
	connector->display_info.height_mm = mode->height_mm;
	drm_mode_probed_add(connector, mode);

	VIRT_DRM_FUNC_EXIT();

	return 1;
}


static const struct drm_panel_funcs panel_canary9527_funcs = {
	.disable   = panel_canary9527_disable,
	.unprepare = panel_canary9527_unprepare,
	.prepare   = panel_canary9527_prepare,
	.enable    = panel_canary9527_enable,
	.get_modes = panel_canary9527_get_modes,
};

static int panel_canary9527_parse_cmd_seq(struct device *dev,
				const u8 *data, int length,
				struct panel_cmd_seq *seq)
{
	struct panel_cmd_header *header;
	struct panel_cmd_desc *desc;
	char *buf, *d;
	unsigned int i, cnt, len;
	VIRT_DRM_FUNC_ENTER();

	if (!seq)
		return -EINVAL;

	buf = devm_kmemdup(dev, data, length, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	d = buf;
	len = length;
	cnt = 0;
	while (len > sizeof(*header)) {
		header = (struct panel_cmd_header *)d;

		d += sizeof(*header);
		len -= sizeof(*header);

		if (header->payload_length > len)
			return -EINVAL;

		d += header->payload_length;
		len -= header->payload_length;
		cnt++;
	}

	if (len)
		return -EINVAL;

	seq->cmd_cnt = cnt;
	seq->cmds = devm_kcalloc(dev, cnt, sizeof(*desc), GFP_KERNEL);
	if (!seq->cmds)
		return -ENOMEM;

	d = buf;
	len = length;
	for (i = 0; i < cnt; i++) {
		header = (struct panel_cmd_header *)d;
		len -= sizeof(*header);
		d += sizeof(*header);

		desc = &seq->cmds[i];
		desc->header = *header;
		desc->payload = d;

		d += header->payload_length;
		len -= header->payload_length;
	}
	VIRT_DRM_FUNC_EXIT();

	return 0;
}

static int panel_canary9527_probe(struct device *dev, const struct panel_desc *desc)
{
	struct panel_canary9527 *panel;
	int err;

	VIRT_DRM_FUNC_ENTER();

	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
	if (!panel)
		return -ENOMEM;

	panel->desc = desc;

	panel->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
	if (IS_ERR(panel->reset_gpio)) {
		dev_err(dev, "cannot get reset-gpio\n");
		return PTR_ERR(panel->reset_gpio);
	}

	panel->lcd_core_supply = devm_regulator_get(dev, "lcd_core");
	if (IS_ERR(panel->lcd_core_supply)) {
		err = PTR_ERR(panel->lcd_core_supply);
		if (err != -EPROBE_DEFER)
			dev_err(dev, "failed to request regulator(lcd_core): %d\n", err);
		return err;
	}

	panel->lcd_io_supply = devm_regulator_get(dev, "lcd_io");
	if (IS_ERR(panel->lcd_io_supply)) {
		err = PTR_ERR(panel->lcd_io_supply);
		if (err != -EPROBE_DEFER)
			dev_err(dev, "failed to request regulator(lcd_io): %d\n", err);
		return err;
	}

	err = regulator_set_voltage(panel->lcd_core_supply, 2850000, 2850000);
	if (err) {
		dev_err(dev, "unable to set the voltage for lcdcore regulator\n");
		return err;
	}
	err = regulator_set_voltage(panel->lcd_io_supply, 1800000, 1800000);
	if (err) {
		dev_err(dev, "unable to set the voltage for lcdio regulator\n");
		return err;
	}

	drm_panel_init(&panel->base, dev, &panel_canary9527_funcs, DRM_MODE_CONNECTOR_DSI);

	/* 背光通过设备树获取 pwm背光 */
	err = drm_panel_of_backlight(&panel->base);
	if (err) {
		dev_err(dev, "failed to register backlight: %d\n", err);
		return err;
	}
	drm_panel_add(&panel->base);
	
	dev_set_drvdata(dev, panel);

	VIRT_DRM_FUNC_EXIT();

	return 0;
}

void dump_mode_info(struct drm_display_mode *mode)
{
	VIRT_DRM_FUNC_ENTER();

	pr_info("clock       :%d\n", mode->clock);
	pr_info("hdisplay    :%d\n", mode->hdisplay);
	pr_info("hsync_start :%d\n", mode->hdisplay);
	pr_info("hsync_end   :%d\n", mode->hsync_end);
	pr_info("htotal      :%d\n", mode->htotal);
	pr_info("vdisplay    :%d\n", mode->vdisplay);
	pr_info("vsync_start :%d\n", mode->vsync_start);
	pr_info("vsync_end   :%d\n", mode->vsync_end);
	pr_info("vtotal      :%d\n", mode->vtotal);
	pr_info("flags       :%d\n", mode->flags);
	pr_info("width_mm    :%d\n", mode->width_mm);
	pr_info("height_mm   :%d\n", mode->height_mm);
	pr_info("flag        :0x%x\n", mode->flags);

	VIRT_DRM_FUNC_EXIT();
}

static int panel_canary9527_of_get_desc_data(struct device *dev,
					 struct panel_desc *desc)
{
	struct device_node *np = dev->of_node;
	struct drm_display_mode *mode;
	u32 bus_flags;
	const void *data;
	int len;
	int err;
	VIRT_DRM_FUNC_ENTER();

	mode = devm_kzalloc(dev, sizeof(*mode), GFP_KERNEL);
	if (!mode)
		return -ENOMEM;

	err = of_get_drm_display_mode(np, mode, &bus_flags, OF_USE_NATIVE_MODE);
	if (!err) {
		dump_mode_info(mode);
		desc->modes = mode;
		desc->num_modes = 1;
		desc->bus_flags = bus_flags;

		of_property_read_u32(np, "bpc", &desc->bpc);
		of_property_read_u32(np, "bus-format", &desc->bus_format);
		of_property_read_u32(np, "width-mm", &desc->size.width);
		of_property_read_u32(np, "height-mm", &desc->size.height);
	}

	of_property_read_u32(np, "prepare-delay-ms", &desc->delay.prepare);
	of_property_read_u32(np, "enable-delay-ms", &desc->delay.enable);
	of_property_read_u32(np, "disable-delay-ms", &desc->delay.disable);
	of_property_read_u32(np, "unprepare-delay-ms", &desc->delay.unprepare);
	//of_property_read_u32(np, "reset-seq-delay-ms", &desc->delay.reset);
	of_property_read_u32_array(np, "reset-seq-delay-ms", &desc->delay.reset, 3);
	device_property_read_u32_array
	of_property_read_u32(np, "init-delay-ms", &desc->delay.init);

	data = of_get_property(np, "panel-init-sequence", &len);
	if (data) {
		desc->init_seq = devm_kzalloc(dev, sizeof(*desc->init_seq),
					      GFP_KERNEL);
		if (!desc->init_seq)
			return -ENOMEM;

		err = panel_canary9527_parse_cmd_seq(dev, data, len,
						 desc->init_seq);
		if (err) {
			dev_err(dev, "failed to parse init sequence\n");
			return err;
		}
	}

	data = of_get_property(np, "panel-exit-sequence", &len);
	if (data) {
		desc->exit_seq = devm_kzalloc(dev, sizeof(*desc->exit_seq),
					      GFP_KERNEL);
		if (!desc->exit_seq)
			return -ENOMEM;

		err = panel_canary9527_parse_cmd_seq(dev, data, len,
						 desc->exit_seq);
		if (err) {
			dev_err(dev, "failed to parse exit sequence\n");
			return err;
		}
	}
	VIRT_DRM_FUNC_EXIT();

	return 0;
}

static int panel_canary9527_dsi_of_get_desc_data(struct device *dev,
					     struct panel_desc_dsi *desc)
{
	struct device_node *np = dev->of_node;
	u32 val;
	int err;
	VIRT_DRM_FUNC_ENTER();

	err = panel_canary9527_of_get_desc_data(dev, &desc->desc);
	if (err)
		return err;

	if (!of_property_read_u32(np, "dsi,flags", &val))
		desc->flags = val;
	if (!of_property_read_u32(np, "dsi,format", &val))
		desc->format = val;
	if (!of_property_read_u32(np, "dsi,lanes", &val))
		desc->lanes = val;
	if (!of_property_read_u32(np, "dsi,hs_rate", &val))
		desc->hs_rate = val;
	if (!of_property_read_u32(np, "dsi,lp_rate", &val))
		desc->lp_rate = val;

	VIRT_DRM_FUNC_EXIT();

	return 0;
}

static const struct of_device_id panel_canary9527_of_match[] = {
	{
		.compatible = "virtual,canary9527",
		.data = NULL,
	},
	{ }
};
MODULE_DEVICE_TABLE(of, panel_canary9527_of_match);

static int panel_canary9527_dsi_probe(struct mipi_dsi_device *dsi)
{
	struct panel_canary9527 *panel;

	const struct panel_desc_dsi *desc;
	struct device *dev = &dsi->dev;
	struct panel_desc_dsi *d;
	const struct of_device_id *id;
	int err;

	VIRT_DRM_FUNC_ENTER();

	id = of_match_node(panel_canary9527_of_match, dsi->dev.of_node);
	if (!id)
		return -ENODEV;

	if (!id->data) {
		d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
		if (!d)
			return -ENOMEM;

		err = panel_canary9527_dsi_of_get_desc_data(dev, d);
		if (err) {
			dev_err(dev, "failed to get desc data: %d\n", err);
			return err;
		}
	}

	desc = id->data ? id->data : d;

	err = panel_canary9527_probe(&dsi->dev, &desc->desc);
	if (err < 0)
		return err;

	panel = dev_get_drvdata(dev);
	panel->dsi = dsi;

	dsi->mode_flags = desc->flags;
	dsi->format = desc->format;
	dsi->lanes = desc->lanes;
	dsi->hs_rate = desc->hs_rate;
	dsi->lp_rate = desc->lp_rate;

	err = mipi_dsi_attach(dsi);
	if (err) {
		struct panel_canary9527 *panel = dev_get_drvdata(&dsi->dev);
		drm_panel_remove(&panel->base);
	}

	VIRT_DRM_FUNC_ENTER();

	return err;
}

static int panel_canary9527_dsi_remove(struct mipi_dsi_device *dsi)
{

	struct panel_canary9527 *panel = mipi_dsi_get_drvdata(dsi);

	mipi_dsi_detach(dsi);

	drm_panel_remove(&panel->base);
	drm_panel_disable(&panel->base);
	drm_panel_unprepare(&panel->base);

	return 0;
}

static struct mipi_dsi_driver panel_canary9527_driver = {
	.probe  = panel_canary9527_dsi_probe,
	.remove = panel_canary9527_dsi_remove,
	.driver = {
		.name = "panel-virtual-canary9527",
		.of_match_table = panel_canary9527_of_match,
	},
};
module_mipi_dsi_driver(panel_canary9527_driver);

MODULE_AUTHOR("Songze Lee <songze_lee@163.com>");
MODULE_DESCRIPTION("DRM driver for canary9527 MIPI DSI panel");
MODULE_LICENSE("GPL v2");

1.3 dts配置

在芯片的virtual_xxx.dtsi增加如下

soc {
		......

		lcdc: display-controller@A0590000 {
			compatible = "virtual,virtsoc-lcdc";
			reg = <0xA0590000 0x1000>;
			clocks = <&lcdc_axi_clk>, <&lcdc0_mclk>;
			clock-names =  "lcdc_axi_clk", "lcdc_mclk";
			interrupts = <0 19 IRQ_TYPE_LEVEL_HIGH>;
			status = "disabled";
		};

		dsi: dsi@A05A0000 {
			compatible = "virtual,virtsoc-dsi";
			reg = <0xA05A0000 0x10000>;
			clocks = <&dsi_cfg_clkgt>, <&dsi_ref_clkgt>;
			clock-names = "pclk", "ref";
			status = "disabled";
		};
	};

在具体的产品dts中增加如下:

		backlight: backlight {
			compatible = "pwm-backlight";
			pwms = <&pwm0 0 5000000>;
			brightness-levels = <
				  0  20  20  21  21  22  22  23
				 23  24  24  25  25  26  26  27
				 27  28  28  29  29  30  30  31
				 31  32  32  33  33  34  34  35
				 35  36  36  37  37  38  38  39
				 40  41  42  43  44  45  46  47
				 48  49  50  51  52  53  54  55
				 56  57  58  59  60  61  62  63
				 64  65  66  67  68  69  70  71
				 72  73  74  75  76  77  78  79
				 80  81  82  83  84  85  86  87
				 88  89  90  91  92  93  94  95
				 96  97  98  99 100 101 102 103
				104 105 106 107 108 109 110 111
				112 113 114 115 116 117 118 119
				120 121 122 123 124 125 126 127
				128 129 130 131 132 133 134 135
				136 137 138 139 140 141 142 143
				144 145 146 147 148 149 150 151
				152 153 154 155 156 157 158 159
				160 161 162 163 164 165 166 167
				168 169 170 171 172 173 174 175
				176 177 178 179 180 181 182 183
				184 185 186 187 188 189 190 191
				192 193 194 195 196 197 198 199
				200 201 202 203 204 205 206 207
				208 209 210 211 212 213 214 215
				216 217 218 219 220 221 222 223
				224 225 226 227 228 229 230 231
				232 233 234 235 236 237 238 239
				240 241 242 243 244 245 246 247
				248 249 250 251 252 253 254 255
			>;
			default-brightness-level = <200>;
		};


&dsi {
	#address-cells = <1>;
	#size-cells = <0>;
	status = "okay";

	ports {
		#address-cells = <1>;
		#size-cells = <0>;

		port@0 {
			reg = <0>;
			dsi_in: endpoint {
				remote-endpoint = <&lcdc_out_dsi>;
			};
		};

		port@1 {
			reg = <1>;
			dsi_out: endpoint {
				remote-endpoint = <&dsi_panel_in>;
			};
		};
	};

	panel-dsi@0 {
		compatible = "virtual,canary9527";
		reg = <0>; /* dsi virtual channel (0..3) */
		reset-gpios = <&gpio 154 0>;
		lcd_core-supply = <&dldo7>;
		lcd_io-supply = <&aldo10>;

		status = "okay";

		backlight = <&backlight>;
		prepare-delay-ms = <2>;
		reset-seq-delay-ms = <20 50 50>;
		init-delay-ms = <20>;
		enable-delay-ms = <120>;
		disable-delay-ms = <50>;
		unprepare-delay-ms = <20>;
		width-mm = <59>;
		height-mm = <104>;

		dsi,hs_rate = <65000000>;
		dsi,lp_rate = <13000000>;
		dsi,flags = <(MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
				      MIPI_DSI_MODE_LPM)>;
		dsi,format = <MIPI_DSI_FMT_RGB888>;
		dsi,lanes = <3>;

		panel-init-sequence = [
			00 23 02 B0 00
			/* TODO */
            ......
            ......
		];
		panel-exit-sequence = [
			14 05 01 28
			DC 05 01 10
		];

		display-timings {
			native-mode = <&dsi_timing0>;

			dsi_timing0: timing0 {
				clock-frequency = <65000000>;
				hactive = <720>;
				vactive = <1280>;
				hfront-porch = <100>;
				hsync-len = <2>;
				hback-porch = <28>;
				vfront-porch = <8>;
				vsync-len = <1>;
				vback-porch = <14>;
				hsync-active = <1>;
				vsync-active = <1>;
			};
		};

		port {
			dsi_panel_in: endpoint {
				remote-endpoint = <&dsi_out>;
			};
		};
	};
};

&lcdc {
	status = "okay";

	port {
		lcdc_out_dsi: endpoint@0 {
			remote-endpoint = <&dsi_in>;
		};
	};
};

2. drm驱动应用测试

2.1 modetest输出彩条

设备文件系统使用的是buildroot-2023.02编译,defconfig中增加如下配置,打开编译libdrm

BR2_PACKAGE_LIBDRM=y
BR2_PACKAGE_LIBDRM_HAS_ATOMIC=y
BR2_PACKAGE_LIBDRM_INSTALL_TESTS=y

需要注意的是/system/usr/bin/modetest -M “you modules name”,不一定支持我们编写的驱动名称。解压dl/libdrm/libdrm-2.4.115.tar.xz, 需要修改dl/libdrm/libdrm-2.4.115/tests/util/kms.c增加drm驱动名称。在static const char * const modules[] 中增加,如“virtsoc”。需要和struct drm_driver名称匹配。

static struct drm_driver virtsoc_drm_driver = {
	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
	.name = "virtsoc",

然后将修改后的源码再压缩成libdrm-2.4.115.tar.xz,重新计算sha256sum,sha512sum校验值,更新到package/libdrm/libdrm.hash, 重新编译即可。

测试经典接口调用drm驱动,输出彩条

/system/usr/bin/modetest -M virtsoc -s 32@35:#0

测试atomic接口:


/system/usr/bin/modetest -M virtsoc -s -a 32@35:#0 -P 33@35:720x1280

在红米2A手机设备上,彩条输出效果如下:

img

2.2 lvgl

拉取lvgl和lv_port_linux源码

git clone https://github.com/lvgl/lvgl.git
git clone https://github.com/lvgl/lv_port_linux.git

将lvgl_port中lvgl空目录删除,将lvgl源码拷贝过来。

修改相关配置文件,如下所示。

diff --git a/Makefile b/Makefile
index 186f9b0..5c3b1cb 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
 #
 # Makefile
 #
-CC 				?= gcc
-CXX				?= g++
+CC 				= /home/linux/code/buildroot-debug/output/host/bin/arm-linux-gnueabi-gcc
+CXX				= /home/linux/code/buildroot-debug/output/host/bin/arm-linux-gnueabi-g++
 LVGL_DIR_NAME 	?= lvgl
 LVGL_DIR 		?= .
 
@@ -12,7 +12,9 @@ WARNINGS		:= -Wall -Wshadow -Wundef -Wmissing-prototypes -Wno-discarded-qualifie
 					-Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body \
 					-Wshift-negative-value -Wstack-usage=2048 -Wno-unused-value -std=gnu99
 CFLAGS 			?= -O3 -g0 -I$(LVGL_DIR)/ $(WARNINGS)
-LDFLAGS 		?= -lm -lstdc++
+LDFLAGS 		?= -lm -lstdc++ -ldrm
+
+CFLAGS += -I/home/linux/code/buildroot-debug/output/host/armeb-buildroot-linux-gnueabi/sysroot/usr/include/drm
 BIN 			= main
 BUILD_DIR 		= ./build
 BUILD_OBJ_DIR 	= $(BUILD_DIR)/obj
diff --git a/lv_conf.h b/lv_conf.h
index d67c319..55dd750 100644
--- a/lv_conf.h
+++ b/lv_conf.h
@@ -1134,7 +1134,7 @@
 #endif
 
 /** Driver for /dev/fb */
-#define LV_USE_LINUX_FBDEV      1
+#define LV_USE_LINUX_FBDEV      0
 #if LV_USE_LINUX_FBDEV
     #define LV_LINUX_FBDEV_BSD           0
     #define LV_LINUX_FBDEV_RENDER_MODE   LV_DISPLAY_RENDER_MODE_PARTIAL
@@ -1168,13 +1168,13 @@
 #endif
 
 /** Driver for /dev/dri/card */
-#define LV_USE_LINUX_DRM        0
+#define LV_USE_LINUX_DRM        1
 
 /** Interface for TFT_eSPI */
 #define LV_USE_TFT_ESPI         0
 
 /** Driver for evdev input devices */
-#define LV_USE_EVDEV    0
+#define LV_USE_EVDEV    1
 
 /** Driver for libinput input devices */
 #define LV_USE_LIBINPUT    0
diff --git a/lvgl b/lvgl
index 4744bbe..de32a11 160000
--- a/lvgl
+++ b/lvgl
@@ -1 +1 @@
-Subproject commit 4744bbe194b492fdbeaeecaf19a7c4a710984a12
+Subproject commit de32a116bcb6f3682aee45c30be425d3a3eed142
diff --git a/main.c b/main.c
index c997aec..c0aeddf 100644
--- a/main.c
+++ b/main.c
@@ -62,7 +62,8 @@ static void lv_linux_init_input_pointer(lv_display_t *disp)
      * 
      * If LV_LINUX_EVDEV_POINTER_DEVICE is not set, automatic evdev disovery will start
      */
-    const char *input_device = getenv("LV_LINUX_EVDEV_POINTER_DEVICE");
+    //const char *input_device = getenv("LV_LINUX_EVDEV_POINTER_DEVICE");
+    const char *input_device = "/dev/input/event1";
 
     if (input_device == NULL) {
         LV_LOG_USER("the LV_LINUX_EVDEV_POINTER_DEVICE environment variable is not set. using evdev automatic discovery.");
@@ -144,8 +145,8 @@ static void configure_simulator(int argc, char **argv)
 
     /* Default values */
     fullscreen = maximize = false;
-    window_width = atoi(getenv("LV_SIM_WINDOW_WIDTH") ? : "800");
-    window_height = atoi(getenv("LV_SIM_WINDOW_HEIGHT") ? : "480");
+    window_width = atoi(getenv("LV_SIM_WINDOW_WIDTH") ? : "720");
+    window_height = atoi(getenv("LV_SIM_WINDOW_HEIGHT") ? : "1280");
 
     /* Parse the command-line options. */
     while ((opt = getopt (argc, argv, "fmw:h:")) != -1) {

然后进行编译

make clean
make

编译完成输出build/bin/main

在红米2A手机设备上执行效果如下图所示。

img


http://www.kler.cn/a/548734.html

相关文章:

  • 【JVM系列】谈一谈JVM调优
  • 【Leetcode】解锁二分查找:突破解题瓶颈的关键技巧
  • Java和JavaScript当中的json对象和json字符串分别讲解
  • 栈的深度解析:从基础实现到高级算法应用——C++实现与实战指南
  • 清华大学×DeepSeek 使用手册 2.0:《DeepSeek如何赋能职场应用?》(文末附下载链接)
  • DeepSeek-R1论文阅读及蒸馏模型部署
  • SpringBoot:使用spring-boot-test对web应用做单元测试时如何测试Filter?
  • C++11新特性之final
  • 【吾爱出品】 视频批量分段工具
  • 物联网行业通识:从入门到深度解析
  • 深入解析SVG图片原理:从基础到高级应用
  • 类和对象(5)——抽象类和接口
  • webassembly009 transformers.js 网页端侧推理 whisper-web的AudioManager组件
  • 42 接雨水
  • 【数据结构】 栈和队列
  • 网剧《一念逍遥》正式启动筹备
  • vLLM专题(二):安装-CPU
  • 【Python】Python入门基础——环境搭建
  • Ubuntu20.04部署stable-diffusion-webui环境小记
  • Leetcode100-春招-矩阵题类