文章目录
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