DRM

概述

DRM 学习笔记,参考 vkms, 写了一份简单的 kms 驱动。

环境:

  • buildroot-2022.11.2
  • qemu_arm_vexpress_defconfig
  • linux-5.15.18

源文件:

  • demo_drv.c
  • demo_drv.h
  • demo_lvds.c
  • demo_crtc.c
  • boadr.dts
  • Makefile

源文件

device tree

在 dts 文件 buildroot-2022.11.2/output/build/linux-5.15.18/arch/arm/boot/dts/vexpress-v2p-ca9.dts 中添加相关节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
drm-demo {
compatible = "drm,display-subsystem";
status = "okay";
ports = <&crtc0>;
};

demo-crtc {
compatible = "demo,drm-crtc";
status = "okay";

crtc0: port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;

crtc0_lvds: endpoint@0 {
reg = <0>;
remote-endpoint = <&lvds_in_0>;
};
};
};

demo-lvds {
compatible = "demo,drm-lvds";
status = "okay";

port@0 {
reg = <0>;

lvds_in_0: endpoint {
remote-endpoint = <&crtc0>;
};
};
};

Makefile

1
2
3
4
5
6
demo-y := \
demo_drv.o \
demo_crtc.o \
demo_lvds.o

obj-$(CONFIG_DRM_DEMO) += demo.o

demo drv

demo_drv.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* SPDX-License-Identifier: GPL-2.0+ */

#ifndef _DEMO_DRV_H_
#define _DEMO_DRV_H_

#include <linux/hrtimer.h>

#include <drm/drm.h>
#include <drm/drm_gem.h>
#include <drm/drm_encoder.h>
#include <drm/drm_writeback.h>


#include <drm/drm_gem.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_file.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_managed.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_gem_shmem_helper.h>
#include <drm/drm_vblank.h>

#include <linux/component.h>

#define XRES_MIN 20
#define YRES_MIN 20

#define XRES_DEF 1024
#define YRES_DEF 768

#define XRES_MAX 4096
#define YRES_MAX 4096

struct demo_drv {
struct drm_device drm;
struct drm_crtc crtc;
void *userspace_facing;
};

extern struct platform_driver demo_lvds_driver;
extern struct platform_driver demo_crtc_driver;

#endif /* _DEMO_DRV_H_ */

demo_drv.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>

#include <drm/drm_gem.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_file.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_managed.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_gem_shmem_helper.h>
#include <drm/drm_vblank.h>

#include <drm/drm_of.h>

#include <drm/drm_print.h>
#include <drm/drm_debugfs.h>

#include "demo_drv.h"

#define DRIVER_NAME "DRM DEMO"
#define DRIVER_DESC "Kernel Mode Setting Demo"
#define DRIVER_DATE "20230310"
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 0

static struct drm_driver driver_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM,
// .dumb_create = drm_gem_shmem_dumb_create,

.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
};

static int compare_of(struct device *dev, void *data)
{
struct device_node *np = data;

if (strcmp(dev->driver->name, "demo-crtc") == 0) {
np = of_get_parent(np);
of_node_put(np);
}

return dev->of_node == np;
}

static const struct drm_mode_config_funcs demo_mode_funcs = {
.fb_create = drm_gem_fb_create,
};

static void demo_drm_mode_config_init(struct drm_device *drm)
{
drm->mode_config.max_width = 4096;
drm->mode_config.max_height = 4096;

drm->mode_config.funcs = &demo_mode_funcs;
}

static int demo_drm_bind(struct device *dev)
{
int ret;
struct demo_drv *drv;
struct drm_device *drm;

drv = devm_drm_dev_alloc(dev, &driver_drm_driver,
struct demo_drv, drm);
if (IS_ERR(drv))
return PTR_ERR(drv);

drm = &drv->drm;

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

drm_mode_config_init(drm);

demo_drm_mode_config_init(drm);

ret = component_bind_all(dev, drm);
if (ret) {
pr_err("component_bind_all failed\n");
}

drm_mode_config_reset(drm);

ret = drm_dev_register(drm, 0);
if (ret) {
pr_err("register drm dev failed\n");
return ret;
}

return 0;
}

static void demo_drm_unbind(struct device *dev)
{
pr_info("%s, %d\n", __func__, __LINE__);
}

static const struct component_master_ops demo_drm_ops = {
.bind = demo_drm_bind,
.unbind = demo_drm_unbind,
};

static int driver_probe(struct platform_device *pdev)
{
int ret;

pr_info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
pr_info("drm demo init\n");
pr_info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

// drm_fbdev_generic_setup(drm, 0);

ret = drm_of_component_probe(&pdev->dev, compare_of, &demo_drm_ops);
if (ret) {
pr_err("drm_of_component_probe failed\n");
} else {
pr_err("drm_of_component_probe\n");
}

return 0;
}

// This function is called before the devm_ resources are released
static int driver_remove(struct platform_device *pdev)
{
struct drm_device *drm = platform_get_drvdata(pdev);

drm_dev_unregister(drm);
drm_atomic_helper_shutdown(drm);

return 0;
}

// This function is called on kernel restart and shutdown
static void driver_shutdown(struct platform_device *pdev)
{
drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
}

static int __maybe_unused driver_pm_suspend(struct device *dev)
{
return drm_mode_config_helper_suspend(dev_get_drvdata(dev));
}

static int __maybe_unused driver_pm_resume(struct device *dev)
{
drm_mode_config_helper_resume(dev_get_drvdata(dev));

return 0;
}

static const struct dev_pm_ops driver_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(driver_pm_suspend, driver_pm_resume)
};

static const struct of_device_id drm_demo_dt_ids[] = {
{ .compatible = "drm,display-subsystem", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, drm_demo_dt_ids);

static struct platform_driver demo_drm_driver = {
.driver = {
.name = "drm-demo",
.of_match_table = drm_demo_dt_ids,
.pm = &driver_pm_ops,
},
.probe = driver_probe,
.remove = driver_remove,
.shutdown = driver_shutdown,
};

static struct platform_driver * const drivers[] = {
&demo_drm_driver,
&demo_lvds_driver,
&demo_crtc_driver,
};

static int __init demo_drm_init(void)
{
return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
}
module_init(demo_drm_init);

static void __exit demo_drm_exit(void)
{
platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
}
module_exit(demo_drm_exit);

lvds

demo_lvds.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/mfd/syscon.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/pinctrl/devinfo.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>

#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>

#include "demo_drv.h"

struct demo_lvds {
struct device *dev;
struct drm_device *drm_dev;
struct device_node *port;

struct drm_connector connector;
struct drm_encoder encoder;

};


static const struct drm_encoder_helper_funcs demo_lvds_encoder_funcs;

static void demo_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}

static const struct drm_connector_funcs demo_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = demo_connector_destroy,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};

static int demo_conn_get_modes(struct drm_connector *connector)
{
int count;

count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX);
drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF);

return count;
}

static const struct drm_connector_helper_funcs demo_conn_helper_funcs = {
.get_modes = demo_conn_get_modes,
};

static const struct of_device_id demo_lvds_dt_ids[] = {
{
.compatible = "demo,drm-lvds",
},
{}
};
MODULE_DEVICE_TABLE(of, demo_lvds_dt_ids);

static int demo_lvds_bind(struct device *dev, struct device *master,
void *data)
{
struct demo_lvds *lvds = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct device_node *np = dev->of_node;
int ret;

lvds->drm_dev = drm_dev;
encoder = &lvds->encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
np);

pr_info("possible_crtcs %d\n", encoder->possible_crtcs);

ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_LVDS);
if (ret < 0) {
DRM_DEV_ERROR(drm_dev->dev,
"failed to initialize encoder: %d\n", ret);
return -1;
}

drm_encoder_helper_add(encoder, &demo_lvds_encoder_funcs);

connector = &lvds->connector;
ret = drm_connector_init(drm_dev, connector,
&demo_connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
if (ret) {
DRM_ERROR("Failed to init connector\n");
return -1;
}

drm_connector_helper_add(connector, &demo_conn_helper_funcs);

ret = drm_connector_attach_encoder(connector, encoder);
if (ret) {
DRM_ERROR("Failed to attach connector to encoder\n");
return -1;
}

pr_info("%s, %d\n", __func__, __LINE__);
return 0;
}

static void demo_lvds_unbind(struct device *dev, struct device *master,
void *data)
{
pr_info("%s, %d\n", __func__, __LINE__);
}

static const struct component_ops demo_lvds_component_ops = {
.bind = demo_lvds_bind,
.unbind = demo_lvds_unbind,
};

static int demo_lvds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct demo_lvds *lvds;
int ret = 0;

pr_info("%s, %d\n", __func__, __LINE__);

lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
if (!lvds)
return -ENOMEM;

dev_set_drvdata(dev, lvds);

ret = component_add(&pdev->dev, &demo_lvds_component_ops);
if (ret < 0) {
pr_err("failed to add component\n");
}

return ret;
}

static int demo_lvds_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &demo_lvds_component_ops);
return 0;
}

struct platform_driver demo_lvds_driver = {
.probe = demo_lvds_probe,
.remove = demo_lvds_remove,
.driver = {
.name = "demo-lvds",
.of_match_table = of_match_ptr(demo_lvds_dt_ids),
},
};

crtc

demo_crtc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/mfd/syscon.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/pinctrl/devinfo.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>

#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>

#include "demo_drv.h"

struct demo_lvds {
struct device *dev;
struct drm_device *drm_dev;
struct device_node *port;

struct drm_connector connector;
struct drm_encoder encoder;

};


static const struct drm_encoder_helper_funcs demo_lvds_encoder_funcs;

static void demo_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}

static const struct drm_connector_funcs demo_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = demo_connector_destroy,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};

static int demo_conn_get_modes(struct drm_connector *connector)
{
int count;

count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX);
drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF);

return count;
}

static const struct drm_connector_helper_funcs demo_conn_helper_funcs = {
.get_modes = demo_conn_get_modes,
};

static const struct of_device_id demo_lvds_dt_ids[] = {
{
.compatible = "demo,drm-lvds",
},
{}
};
MODULE_DEVICE_TABLE(of, demo_lvds_dt_ids);

static int demo_lvds_bind(struct device *dev, struct device *master,
void *data)
{
struct demo_lvds *lvds = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct device_node *np = dev->of_node;
int ret;

lvds->drm_dev = drm_dev;
encoder = &lvds->encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
np);

pr_info("possible_crtcs %d\n", encoder->possible_crtcs);

ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_LVDS);
if (ret < 0) {
DRM_DEV_ERROR(drm_dev->dev,
"failed to initialize encoder: %d\n", ret);
return -1;
}

drm_encoder_helper_add(encoder, &demo_lvds_encoder_funcs);

connector = &lvds->connector;
ret = drm_connector_init(drm_dev, connector,
&demo_connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
if (ret) {
DRM_ERROR("Failed to init connector\n");
return -1;
}

drm_connector_helper_add(connector, &demo_conn_helper_funcs);

ret = drm_connector_attach_encoder(connector, encoder);
if (ret) {
DRM_ERROR("Failed to attach connector to encoder\n");
return -1;
}

pr_info("%s, %d\n", __func__, __LINE__);
return 0;
}

static void demo_lvds_unbind(struct device *dev, struct device *master,
void *data)
{
pr_info("%s, %d\n", __func__, __LINE__);
}

static const struct component_ops demo_lvds_component_ops = {
.bind = demo_lvds_bind,
.unbind = demo_lvds_unbind,
};

static int demo_lvds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct demo_lvds *lvds;
int ret = 0;

pr_info("%s, %d\n", __func__, __LINE__);

lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
if (!lvds)
return -ENOMEM;

dev_set_drvdata(dev, lvds);

ret = component_add(&pdev->dev, &demo_lvds_component_ops);
if (ret < 0) {
pr_err("failed to add component\n");
}

return ret;
}

static int demo_lvds_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &demo_lvds_component_ops);
return 0;
}

struct platform_driver demo_lvds_driver = {
.probe = demo_lvds_probe,
.remove = demo_lvds_remove,
.driver = {
.name = "demo-lvds",
.of_match_table = of_match_ptr(demo_lvds_dt_ids),
},
};