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

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值