不管什么设备输出,使用drm框架,都要做几个步骤:fb、crtc、plane、encoder和connector初始化;
以xilinx异构平台设计在FPGA端的HDMI为例,跟读代码分析:
1.HDMI驱动模块
这里涉及到就是encoder和connector的初始化:
static int xlnx_drm_hdmi_bind(struct device *dev, struct device *master,
void *data)
{
struct xlnx_drm_hdmi *xhdmi = dev_get_drvdata(dev);
struct drm_encoder *encoder = &xhdmi->encoder;
struct drm_device *drm_dev = data;
int ret;
/*
* TODO: The possible CRTCs are 1 now as per current implementation of
* HDMI tx driver. DRM framework can support more than one CRTCs and
* HDMI driver can be enhanced for that.
*/
encoder->possible_crtcs = 1;
/* initialize encoder */
drm_encoder_init(drm_dev, encoder, &xlnx_drm_hdmi_encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
drm_encoder_helper_add(encoder, &xlnx_drm_hdmi_encoder_helper_funcs);
/* create connector */
ret = xlnx_drm_hdmi_create_connector(encoder);
if (ret) {
dev_err(xhdmi->dev, "failed creating connector, ret = %d\n", ret);
drm_encoder_cleanup(encoder);
}
return ret;
}
static const struct component_ops xlnx_drm_hdmi_component_ops = {
.bind = xlnx_drm_hdmi_bind,
.unbind = xlnx_drm_hdmi_unbind
};
component_add(xhdmi->dev, &xlnx_drm_hdmi_component_ops);
标记1:通过component框架来管理,这里添加该组件,待平台端匹配并获取;
2.平台端操作:
pl端的设备树:
v_drm_dmaengine_drv: drm-dmaengine-drv {
compatible = "xlnx,pl-disp";
dmas = <&v_frmbuf_rd 0>;
dma-names = "dma0";
xlnx,vformat = "BG24";
#address-cells = <1>;
#size-cells = <0>;
dmaengine_port: port@0 {
reg = <0>;
dmaengine_crtc: endpoint {
remote-endpoint = <&hdmi_encoder>;
};
};
};
static int xlnx_pl_disp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *vtc_node;
struct xlnx_pl_disp *xlnx_pl_disp;
int ret;
const char *vformat;
struct dma_chan *dma_chan;
struct xlnx_dma_chan *xlnx_dma_chan;
xlnx_pl_disp = devm_kzalloc(dev, sizeof(*xlnx_pl_disp), GFP_KERNEL);
if (!xlnx_pl_disp)
return -ENOMEM;
//请求分配dma通道
dma_chan = of_dma_request_slave_channel(dev->of_node, "dma0");
if (IS_ERR_OR_NULL(dma_chan)) {
dev_err(dev, "failed to request dma channel\n");
return PTR_ERR(dma_chan);
}
xlnx_dma_chan = devm_kzalloc(dev, sizeof(*xlnx_dma_chan), GFP_KERNEL);
if (!xlnx_dma_chan)
return -ENOMEM;
xlnx_dma_chan->dma_chan = dma_chan;
xlnx_pl_disp->chan = xlnx_dma_chan;
ret = of_property_read_string(dev->of_node, "xlnx,vformat", &vformat);
if (ret) {
dev_err(dev, "No xlnx,vformat value in dts\n");
goto err_dma;
}
strcpy((char *)&xlnx_pl_disp->fmt, vformat);
/* VTC Bridge support */
vtc_node = of_parse_phandle(dev->of_node, "xlnx,bridge", 0);
if (vtc_node) {
xlnx_pl_disp->vtc_bridge = of_xlnx_bridge_get(vtc_node);
if (!xlnx_pl_disp->vtc_bridge) {
dev_info(dev, "Didn't get vtc bridge instance\n");
return -EPROBE_DEFER;
}
} else {
dev_info(dev, "vtc bridge property not present\n");
}
xlnx_pl_disp->dev = dev;
platform_set_drvdata(pdev, xlnx_pl_disp);
ret = component_add(dev, &xlnx_pl_disp_component_ops);
if (ret)
goto err_dma;
xlnx_pl_disp->master = xlnx_drm_pipeline_init(pdev);
if (IS_ERR(xlnx_pl_disp->master)) {
ret = PTR_ERR(xlnx_pl_disp->master);
dev_err(dev, "failed to initialize the drm pipeline\n");
goto err_component;
}
dev_info(&pdev->dev, "Xlnx PL display driver probed\n");
return 0;
err_component:
component_del(dev, &xlnx_pl_disp_component_ops);
err_dma:
dma_release_channel(xlnx_pl_disp->chan->dma_chan);
return ret;
}
在这里同样可以看到也添加一个组件:
component_add(dev, &xlnx_pl_disp_component_ops);
static const struct component_ops xlnx_pl_disp_component_ops = {
.bind = xlnx_pl_disp_bind,
.unbind = xlnx_pl_disp_unbind,
};
static int xlnx_pl_disp_bind(struct device *dev, struct device *master,
void *data)
{
struct drm_device *drm = data;
struct xlnx_pl_disp *xlnx_pl_disp = dev_get_drvdata(dev);
int ret;
u32 *fmts = NULL;
unsigned int num_fmts = 0;
/* in case of fb IP query the supported formats and there count */
xilinx_xdma_get_drm_vid_fmts(xlnx_pl_disp->chan->dma_chan,
&num_fmts, &fmts);
ret = drm_universal_plane_init(drm, &xlnx_pl_disp->plane, 0,
&xlnx_pl_disp_plane_funcs,
fmts ? fmts : &xlnx_pl_disp->fmt,
num_fmts ? num_fmts : 1,
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
if (ret)
return ret;
drm_plane_helper_add(&xlnx_pl_disp->plane,
&xlnx_pl_disp_plane_helper_funcs);
ret = drm_crtc_init_with_planes(drm, &xlnx_pl_disp->xlnx_crtc.crtc,
&xlnx_pl_disp->plane, NULL,
&xlnx_pl_disp_crtc_funcs, NULL);
if (ret) {
drm_plane_cleanup(&xlnx_pl_disp->plane);
return ret;
}
drm_crtc_helper_add(&xlnx_pl_disp->xlnx_crtc.crtc,
&xlnx_pl_disp_crtc_helper_funcs);
xlnx_pl_disp->xlnx_crtc.get_format = &xlnx_pl_disp_get_format;
xlnx_pl_disp->xlnx_crtc.get_align = &xlnx_pl_disp_get_align;
xlnx_pl_disp->drm = drm;
xlnx_crtc_register(xlnx_pl_disp->drm, &xlnx_pl_disp->xlnx_crtc);
return 0;
}
其操作就是crtc、plane的初始化;
标记2:那组件集合里(一个hdmi驱动模块处添加的,一个平台pl disp添加)是如何去匹配配对的呢,在刚才disp_pl添加组件后执行如下函数:
xlnx_pl_disp->master = xlnx_drm_pipeline_init(pdev);
Path: xlnx_drv.c(drivers/gpu/drm/xlnx)
struct platform_device *xlnx_drm_pipeline_init(struct platform_device *pdev)
{
struct platform_device *master;
int id, ret;
id = ffs(xlnx_master_ids);
if (!id)
return ERR_PTR(-ENOSPC);
master = platform_device_alloc("xlnx-drm", id - 1);
if (!master)
return ERR_PTR(-ENOMEM);
master->dev.parent = &pdev->dev;
ret = platform_device_add(master);
if (ret)
goto err_out;
WARN_ON(master->id != id - 1);
xlnx_master_ids &= ~BIT(master->id);
return master;
err_out:
platform_device_unregister(master);
return ERR_PTR(ret);
}
通过xlnx-drm匹配后执行,这里先说下结果:组件匹配后执行所有的bind操作,crtc、plane、encoder、connector都将初始化,
跟读代码详细了解下:
static int xlnx_platform_probe(struct platform_device *pdev)
{
return xlnx_of_component_probe(&pdev->dev, xlnx_compare_of,
&xlnx_master_ops);
}
static int xlnx_of_component_probe(struct device *master_dev,
int (*compare_of)(struct device *, void *),
const struct component_master_ops *m_ops)
{
struct device *dev = master_dev->parent;
struct device_node *ep, *port, *remote, *parent;
struct component_match *match = NULL;
int i;
if (!dev->of_node)
return -EINVAL;
printk("++++++++++++xlnx_of_component_probe:%s\n", dev->of_node->name);
//初始化match对象
component_match_add(master_dev, &match, compare_of, dev->of_node);
for (i = 0; ; i++) {
port = of_parse_phandle(dev->of_node, "ports", i);
if (!port)
break;
parent = port->parent;
if (!of_node_cmp(parent->name, "ports"))
parent = parent->parent;
parent = of_node_get(parent);
if (!of_device_is_available(parent)) {
of_node_put(parent);
of_node_put(port);
continue;
}
printk("++++++++++++xlnx_of_component_probe parent :%s\n", parent->name);
component_match_add(master_dev, &match, compare_of, parent);
of_node_put(parent);
of_node_put(port);
}
parent = dev->of_node;
for (i = 0; ; i++) {
parent = of_node_get(parent);
if (!of_device_is_available(parent)) {
of_node_put(parent);
continue;
}
//remote 节点node remote-endpoint = <&hdmi_encoder>; 父节点:v_hdmi_tx_ss
//match对象初始化父节点
for_each_endpoint_of_node(parent, ep) {
remote = of_graph_get_remote_port_parent(ep);
if (!remote || !of_device_is_available(remote) ||
remote == dev->of_node) {
of_node_put(remote);
continue;
} else if (!of_device_is_available(remote->parent)) {
dev_warn(dev, "parent dev of %s unavailable\n",
remote->full_name);
of_node_put(remote);
continue;
}
printk("++++++++++++xlnx_of_component_probe remote :%s\n", remote->name);
component_match_add(master_dev, &match, compare_of,
remote);
of_node_put(remote);
}
of_node_put(parent);
port = of_parse_phandle(dev->of_node, "ports", i);
if (!port)
break;
parent = port->parent;
if (!of_node_cmp(parent->name, "ports"))
parent = parent->parent;
of_node_put(port);
}
//找到匹配的master,并执行所以bind
return component_master_add_with_match(master_dev, m_ops, match);
}
static int try_to_bring_up_master(struct master *master,
struct component *component)
{
int ret;
dev_dbg(master->dev, "trying to bring up master\n");
//匹配
if (find_components(master)) {
dev_dbg(master->dev, "master has incomplete components\n");
return 0;
}
if (component && component->master != master) {
dev_dbg(master->dev, "master is not for this component (%s)\n",
dev_name(component->dev));
return 0;
}
if (!devres_open_group(master->dev, NULL, GFP_KERNEL))
return -ENOMEM;
//找到了所有组件,则执行xlnx_bind(xlnx_drv.c)
/* Found all components */
ret = master->ops->bind(master->dev);
if (ret < 0) {
devres_release_group(master->dev, NULL);
dev_info(master->dev, "master bind failed: %d\n", ret);
return ret;
}
master->bound = true;
return 1;
}
static int find_components(struct master *master)
{
struct component_match *match = master->match;
size_t i;
int ret = 0;
/*
* Scan the array of match functions and attach
* any components which are found to this master.
*/
//num为2,前面添加两个match
for (i = 0; i < match->num; i++) {
struct component_match_array *mc = &match->compare[i];
struct component *c;
dev_dbg(master->dev, "Looking for component %zu\n", i);
if (match->compare[i].component)
continue;
//对比:match的node和已添加component_list的node
//第一个匹配到的是v_drm_dmaengine_drv
//第二个匹配成功的是v_hdmi_tx_ss
c = find_component(master, mc->compare, mc->data);
if (!c) {
ret = -ENXIO;
break;
}
dev_dbg(master->dev, "found component %s, duplicate %u\n", dev_name(c->dev), !!c->master);
/* Attach this component to the master */
match->compare[i].duplicate = !!c->master;
match->compare[i].component = c;
c->master = master;
}
return ret;
}
这里匹配的工作大体是:match的node和已添加component_list的node匹配:
match:
component:
匹配成功则执行xlnx_bind,即drm所有组件初始化和drm设备注册。
static int xlnx_bind(struct device *dev)
{
struct xlnx_drm *xlnx_drm;
struct drm_device *drm;
const struct drm_format_info *info;
struct platform_device *master = to_platform_device(dev);
struct platform_device *pdev = to_platform_device(dev->parent);
int ret;
u32 format;
drm = drm_dev_alloc(&xlnx_drm_driver, &pdev->dev);
if (IS_ERR(drm))
return PTR_ERR(drm);
xlnx_drm = devm_kzalloc(drm->dev, sizeof(*xlnx_drm), GFP_KERNEL);
if (!xlnx_drm) {
ret = -ENOMEM;
goto err_drm;
}
drm_mode_config_init(drm);
drm->mode_config.funcs = &xlnx_mode_config_funcs;
ret = drm_vblank_init(drm, 1);
if (ret) {
dev_err(&pdev->dev, "failed to initialize vblank\n");
goto err_xlnx_drm;
}
drm->irq_enabled = 1;
drm->dev_private = xlnx_drm;
xlnx_drm->drm = drm;
xlnx_drm->master = master;
drm_kms_helper_poll_init(drm);
platform_set_drvdata(master, xlnx_drm);
xlnx_drm->crtc = xlnx_crtc_helper_init(drm);
if (IS_ERR(xlnx_drm->crtc)) {
ret = PTR_ERR(xlnx_drm->crtc);
goto err_xlnx_drm;
}
ret = component_bind_all(&master->dev, drm);
if (ret)
goto err_crtc;
xlnx_mode_config_init(drm);
drm_mode_config_reset(drm);
dma_set_mask(drm->dev, xlnx_crtc_helper_get_dma_mask(xlnx_drm->crtc));
format = xlnx_crtc_helper_get_format(xlnx_drm->crtc);
info = drm_format_info(format);
if (info && info->depth && info->cpp[0]) {
unsigned int align;
align = xlnx_crtc_helper_get_align(xlnx_drm->crtc);
xlnx_drm->fb = xlnx_fb_init(drm, info->cpp[0] * 8, 1, align,
xlnx_fbdev_vres);
if (IS_ERR(xlnx_drm->fb)) {
dev_err(&pdev->dev,
"failed to initialize drm fb\n");
xlnx_drm->fb = NULL;
}
} else {
/* fbdev emulation is optional */
dev_info(&pdev->dev, "fbdev is not initialized\n");
}
ret = drm_dev_register(drm, 0);
if (ret < 0)
goto err_fb;
return 0;
err_fb:
if (xlnx_drm->fb)
xlnx_fb_fini(xlnx_drm->fb);
component_unbind_all(drm->dev, drm);
err_crtc:
xlnx_crtc_helper_fini(drm, xlnx_drm->crtc);
err_xlnx_drm:
drm_mode_config_cleanup(drm);
err_drm:
drm_dev_unref(drm);
return ret;
}