#使用nanopiM3(s5p6818), LINUX 内核4.4.172. 使用HDMI接口显示图像.
#源码:usb2hdmi: 使用usb传输FB数据,hdmi接口输出.
int dri_fd = open("/dev/dri/card0",O_RDWR | O_CLOEXEC);
ioctl(dri_fd, DRM_IOCTL_SET_MASTER, 0);
res.fb_id_ptr=(uint64_t)res_fb_buf;
res.crtc_id_ptr=(uint64_t)res_crtc_buf;
res.connector_id_ptr=(uint64_t)res_conn_buf;
res.encoder_id_ptr=(uint64_t)res_enc_buf;
ioctl(dri_fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
conn.connector_id=res_conn_buf[i];
conn.modes_ptr=(uint64_t)conn_mode_buf;
ioctl(dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn);
create_dumb.width = conn_mode_buf[0].hdisplay;
create_dumb.height = conn_mode_buf[0].vdisplay;
create_dumb.bpp = 32;
ioctl(dri_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
ioctl(dri_fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb);
map_dumb.handle=create_dumb.handle;
ioctl(dri_fd,DRM_IOCTL_MODE_MAP_DUMB,&map_dumb);
fb_base[i] = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, dri_fd, map_dumb.offset);
fb_w[i]=create_dumb.width;
fb_h[i]=create_dumb.height;
enc.encoder_id=conn.encoder_id;
ioctl(dri_fd, DRM_IOCTL_MODE_GETENCODER, &enc);
crtc.crtc_id=enc.crtc_id;
ioctl(dri_fd, DRM_IOCTL_MODE_GETCRTC, &crtc);
crtc.fb_id=cmd_dumb.fb_id;
crtc.set_connectors_ptr=(uint64_t)&res_conn_buf[i];
crtc.mode=conn_mode_buf[0];
ioctl(dri_fd, DRM_IOCTL_MODE_SETCRTC, &crtc);
ioctl(dri_fd, DRM_IOCTL_DROP_MASTER, 0);
/* 写FB */
int x,y;
for (i=0;i<100;i++)
{
int j;
for (j=0;j
#1
int dri_fd = open("/dev/dri/card0",O_RDWR | O_CLOEXEC);
#应用层open() => DRM drm_stub_open() => nx_drm_driver->nx_drm_fops->drm_open
#此步骤只是初始化一些链表而已.
#2
ioctl(dri_fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
res.connector_id_ptr=(uint64_t)res_conn_buf;
#对应 drm_mode_getresources()
#遍历drm->mode_config.connector_list和drm->mode_config.crtc_list链表,
#找到所有的connector和crtc的ID,返回给应用程序.
#3
#conn 为输入参数, 传给ioctl
conn.connector_id=res_conn_buf[i];
#这里保存#2步骤获得的connector_id, 即确定最终要显示图像的显示器(屏幕).
conn.modes_ptr=(uint64_t)conn_mode_buf;
ioctl(dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn);
#此ioctl步骤, 最终调用drm_mode_getresources(), 获取该显示器所支持的显示模式,
#即是显示分辨率,刷新率等信息.
#驱动将根据传入的connector_id, 确定connector.
#当前情景是使用HDMI接口进行显示,驱动使用i2c总线读取显示器的EDID,并进行解析,见panel_hdmi_ops_get_modes().
#将得到的个个显示模式, 加入connector->modes队列. 见drm_mode_connector_list_update().
#3
create_dumb.width = conn_mode_buf[0].hdisplay;
create_dumb.height = conn_mode_buf[0].vdisplay;
create_dumb.bpp = 32;
ioctl(dri_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
#此ioctl最终调用 drm_mode_create_dumb_ioctl()
#应用层输入必须的参数: 长,宽,每个像素大小(bpp), 长x宽xBPP为期望申请的FB的大小.
#再进行一些对齐工作,即为最终要申请的FB的大小. 见nx_drm_gem_dma_alloc().
#4
cmd_dumb.handle=create_dumb.handle;
ioctl(dri_fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb);
#对应drm_mode_addfb2()
#通过handle索引到对于的fb, 将此fb加入drm->mode_config.fb_list链表.
# 将此fb->base加入 drm->mode_config.crtc_idr 链表..., 并返回对应的ID号, 将此ID号返回应用程序.
# 见drm_framebuffer_init()
#5
ioctl(dri_fd,DRM_IOCTL_MODE_MAP_DUMB,&map_dumb);
#对应drm_mode_mmap_dumb_ioctl()
#与mmap相关.
#6
fb_base[i] = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, dri_fd, map_dumb.offset);
#对应nx_drm_gem_fops_mmap(). 内存映射, 使得应用程序可以直接写FB, 无需任何write()和ioctrl().
#7
enc.encoder_id=conn.encoder_id;
ioctl(dri_fd, DRM_IOCTL_MODE_GETENCODER, &enc);
#根据connector所连接的encoder的ID,得到对应encoder的结构体.
crtc.crtc_id=enc.crtc_id;
ioctl(dri_fd, DRM_IOCTL_MODE_GETCRTC, &crtc);
#根据encoder结构体内的ctrc_id信息,获得对应的CRTC结构体.
#8
crtc.fb_id=cmd_dumb.fb_id;
crtc.set_connectors_ptr=(uint64_t)&res_conn_buf[i];
crtc.mode=conn_mode_buf[0];
ioctl(dri_fd, drm_ioctl_mode_setcrtc, &crtc);
#对应drm_mode_setcrtc()
#应用程序传入必须参数:FB_ID, connector_id(包含于set_connectors_ptr), 欲设置的显示模式(conn_mode_buf)
#1. 使用crtc_id 遍历drm->mode_config.crtc_list 找到对应的crtc.
# 使用connector_id 遍历 drm->mode_config.connector_list 找到对应 connector.
# 使用fb_id 遍历 drm->mode_config.crtc_idr 找到对应的 FB.
#2. 使用crtc,connector 找到对应的硬件寄存器, 使用conn_mode_buf的参数设置寄存器.
#3. 将FB的物理地址写入硬件寄存器, 以此确定要显示的内容. nx_mlc_set_rgblayer_address()
#4. 正常地进行剩余的寄存器设置,这些基本是固定设置.无需使用使用应用程序的传来的参数.
#总的而已, 在set_crtc之前的所以操作, 是在收集crtc,encoder,connector.
#先确定connector_id, 根据connector_id找到对应的encoder_id, 根据encoder_id找到对应的crtc_id.
#这样,完整的图像信号传输路径就完成了.
#根据这些id找到对应的结构体.
#借助crtc,encoder结构体,可以找到对应的硬件的寄存器地址(这些地址读取自dts).
#而根据connector结构体, 可以获得connector支持的显示模式,
#应用程序便可以选择其中一种显示模式, 创建对应大小的FB, 并传给set_crtc(), 确定要显示的内容.
#补充:
#当前例程仅使用到一个plane,即一个FB. 当要使用第二个plane时, 需要使用到DRM框架的 drm_mode_setplane()函数.
#形成FB => plane => crtc的关系.
#PLANE
#图层.可以多图层,分辨率不同,位置不同,相互叠加显示于屏幕上.
#譬如显示linux桌面, 会使用两个plane, 一个用于显示鼠标, 一个用于显示其他东西,桌面,图标,应用..
#当前情景仅使用一个plane.
#FB
#一个plane对应于一个fb.
#GEM
#分配FB.
#CONNECTOR
#屏幕. 譬如, HDMI屏幕, lvds屏幕. 即各种接口的屏幕.
#ENCODER
#图像信号转换器. 连接各种屏幕(connector), 将其他图像信号转为对应屏幕的信号.
#CRTC
#目前我认为crtc 在DRM框架上有以下两个作用:
#1. 保存显示模式的参数.分辨率,刷新率等.
#2. 作为图像信号传输的起点, 而connector为终点. 因为只是作为起点,可以对应于任何在图像信号传输路径
# 上处于起点的硬件.
# 图像信号传输路径上, 可以有多个crtc, encoder 和 connector.
# 譬如我使用的nanopiM3(s5p6818), 其硬件上的图像信号传输路径为一下:
mlc(multi layer controler0) -> dpc(displayer controler0)--|
|-->hdmi encoder -> "hdmi 屏幕"
|-->lvds encoder -> "lvds 屏幕"
mlc(multi layer controler1) -> dpc(displayer controler1)--|
#其中 mlc和dpc对应 crtc, 则这里会有两个crtc.
#mlc 是多图层控制器, 6818的mlc提供3个图层(plane)的支持, 图层按需开启和使用.
#mlc将图层混合后,传给dpc(显示控制器).
#例子: s3c2440 仅拥有一个显示控制器 和一块RGB的屏幕.
FB0,FB1 => plane => crtc("2440显示控制器") => encoder ("空") => connector("RGB屏幕")
#2440 没有mlc多图层控制器, 所以只支持一个plane, 可能多图层混合的工作由软件完成吧?
#2440 没有encoder, 2440显示控制器 输出的是RGB信号, 只能接入RGB屏幕. 若添加上RGB转HDMI的HDMI_encoder,
#就可以接入HDMI屏幕了. 所以这里的encoder可以为空.
#1. drm_core_init()
# 申请DRM_MAJOR(226) 为主设备号.
#2. panel_hdmi_probe()
# 是HDMI encoder设备的初始化流程.
#panel_hdmi_get_display() 获取dts上的HDMI的寄存器地址.
#hdmi_ops_open() 读状态寄存器, 开启HDMI电缆插入和拔出中断
#panel_hdmi_parse_dt() 获得与该HDMI encoder配合的i2c 控制器,将在以后用于读取EDID.
#3. nx_drm_bind()
# 是crtc(mlc, dpc)设备的初始化流程
# 创建 struct drm_device *drm_dev; 结构体, 此结构体相当于 一般设备驱动中的 struct device *dev...
# 初始化 drm_dev->mode_config 上的队列.
# nx_drm_crtc_create() 创建crtc 结构体, 解析dts,获得该设备的寄存器地址.
# crtc结构体 加入drm_dev->mode_config->crtc_list.
# drm_minor_alloc() 获得次设备号, 设置设备类型为 legacy, 设备节点名为 "cardx"
# drm_minor_register() 注册字符设备.
#4 nx_drm_connector_attach()
# 创建 struct drm_connector *connector; 结构体, 加入drm_dev->mode_config->connector_list 链表.
# 创建 struct drm_encoder *encoder; 结构体, 加入drm_dev->mode_config->encoder_list 链表.
# 读HDMI状态寄存器, 判断HDMI电缆是否插入. hdmi_ops_is_connected()
# 详细的各个结构体的作用,及其包含的信息, 见drm_read.c · suiren/usb2hdmi - Gitee.com