Rockchip RK3399 - DRM eDP驱动程序
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4
开发板
eMMC
:16GB
LPDDR3
:4GB
显示屏 :15.6
英寸HDMI
接口显示屏
u-boot
:2023.04
linux
:6.3
----------------------------------------------------------------------------------------------------------------------------
在《Rockchip RK3399 - DRM eDP
介绍》我们已经对eDP
进行了详细的介绍,本节我们选择DRM eDP
驱动程序作为分析的对象。
一、设备树配置
1.1 eDP
设备节点
eDP
驱动位于:
drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
;drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
;drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c
;drivers/phy/rockchip/phy-rockchip-dp.c
;
设备节点的配置参考文档 :
Documentation/devicetree/bindings/display/bridge/analogix_dp.txt
;Documentation/devicetree/bindings/display/rockchip/analogix_dp-rockchip.txt
;
设备节点vopb
下的子节点vopb_out_edp
通过edp_in_vopb
(由remote-endpoint
属性指定)和eDP
显示接口组成一个连接通路;
设备节点vopl
下的子节点vopl_out_edp
通过edp_in_vopl
(由remote-endpoint
属性指定)和eDP
显示接口组成一个连接通路;
在arch/arm64/boot/dts/rockchip/rk3399.dtsi
定义有edp
设备节点:
edp: edp@ff970000 {
compatible = "rockchip,rk3399-edp";
reg = <0x0 0xff970000 0x0 0x8000>;
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = <&cru PCLK_EDP>, <&cru PCLK_EDP_CTRL>, <&cru PCLK_VIO_GRF>;
clock-names = "dp", "pclk", "grf";
pinctrl-names = "default";
pinctrl-0 = <&edp_hpd>;
power-domains = <&power RK3399_PD_EDP>;
resets = <&cru SRST_P_EDP_CTRL>;
reset-names = "dp";
rockchip,grf = <&grf>;
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
edp_in: port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
edp_in_vopb: endpoint@0 {
reg = <0>;
remote-endpoint = <&vopb_out_edp>;
};
edp_in_vopl: endpoint@1 {
reg = <1>;
remote-endpoint = <&vopl_out_edp>;
};
};
};
};
可参考:Documentation/devicetree/bindings/display/rockchip/analogix_dp-rockchip.txt
。
1.1.1 子节点ports
子节点ports
包含2个input endpoint
,分别连接到VOPL
和VOPB
;也就是在rk3399
上,eDP
可以和VOPL
、VOPB
连接;
因此可以得到有2条通路:
vopb_out_edp
--->edp_in_vopb
;vopl_out_edp
--->edp_in_vopl
;
需要注意的是,⼀个显⽰接口在同⼀个时刻只能和⼀个VOP
连接,所以在具体的板级配置中,需要设备树中把要使⽤的通路打开,把不使⽤的通路设置为disabled
状态。
1.1.2 interrupts
interrupts
属性的值为 <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH 0>
,含义如下:
GIC_SPI
: 中断控制器的类型,这里指的是ARM
通用中断控制器 (GIC
);10
: 中断的编号,这里是第10
个中断。IRQ_TYPE_LEVEL_HIGH
: 中断的触发方式,这里是高电平触发;0
: 附加参数,这里没有使用;
1.1.3 pinctrl-0
定义了eDP
设备热插拔引脚的默认状态pinctrl-0 = <&edp_hpd>
,edp_hpd
定义在arch/arm64/boot/dts/rockchip/rk3399.dtsi
;
pinctrl: pinctrl {
......
edp {
edp_hpd: edp-hpd {
rockchip,pins =
<4 RK_PC7 2 &pcfg_pull_none>;
};
};
}
这里将GPIO4_C7
引脚功能配置为功能2,实际上NanoPC-T4
开发板eDP
接线中并没有使用HDP
引脚进行热插拔检测。
如果我想开启双屏,比如同时支持HDMI
、eDP
,由于GPIO4_C7
同时被HDMI
和eDP
引用了,因此需要修改arch/arm64/boot/dts/rockchip/rk3399-evb.dtsi
屏蔽掉 pinctrl-0 = <&hdmi_cec>;
;
&hdmi {
ddc-i2c-bus = <&i2c7>;
# pinctrl-names = "default"; 删除
# pinctrl-0 = <&hdmi_cec>; 删除
status = "okay";
};
1.1.4 resets
resets = <&cru SRST_P_EDP_CTRL>
。
1.2 HD702E
触摸显示屏配置
1.2.1 backlight
PWM
驱动位于drivers/video/backlight/pwm_bl.c
,设备节点的配置参考文档 :
Documentation/devicetree/bindings/leds/backlight/pwm-backlight.yaml
:
在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中新增触摸显示屏背光配置设备节点,如下:
backlight: backlight {
compatible = "pwm-backlight";
brightness-levels = <
0 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
200 201 202 203 204 205 206 207
208 209 210 211 212 213 214 215
216 217 218 219 220 221 222 223
224 225 226 227 228 229 230 231
232 233 234 235 236 237 238 239
240 241 242 243 244 245 246 247
248 249 250 251 252 253 254 255>;
default-brightness-level = <200>;
pwms = <&pwm0 0 25000 0>;
status = "okay";
enable-gpios = <&gpio4 RK_PD5 GPIO_ACTIVE_HIGH>;
};
其中:
pwms
:配置PWM
,这里指定为pwm
的phandle
,后面有三个参数:- 参数1:表示
index
(per-chip index of the PWM to request
),一般是0
,因为Rockchip PWM
每个
chip
只有一个; - 参数2:表示
PWM
输出波形的时间周期,单位是ns
;上面我们配置的25000
就是表示想要得到的
PWM
输出周期是40KHz
; - 参数3:表示极性,为可选参数;
- 配置为
0
,那么cooling-levels
描述的是高电平的占空比; - 配置为
PWM_POLARITY_INVERTED
,那么cooling-levels
描述的是低电平的占空比;
- 配置为
- 参数1:表示
brightness-levels
属性:配置背光亮度数组,一般以值255
为一个scale
;-
- 当
PWM
设置为正极性时,从0~255
表示背光为正极,占空比从0%~100%
变化,255~0
为负极性,占空比从100%~0%
变化; - 当
PWM
设置为负极性时,反之;
- 当
default-brightness-level
属性:开机时默认背光亮度,范围为0-255
;enable-gpios
:背光使能脚,高电平有效,连接RK3399
得GPIO4_D5
引脚,通过GPIO
控制背光开关;
1.2.2 启用pwm0
PWM
驱动位于drivers/pwm/pwm-rockchip.c
,设备节点的配置参考文档 :
Documentation/devicetree/bindings/pwm/pwm.yaml
:Documentation/devicetree/bindings/pwm/pwm.txt
:
在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中启用pwm0
设备节点;
&pwm0 {
status = "okay";
};
pwm0
定义在arch/arm64/boot/dts/rockchip/rk3399.dtsi
文件;
pwm0: pwm@ff420000 {
compatible = "rockchip,rk3399-pwm", "rockchip,rk3288-pwm";
reg = <0x0 0xff420000 0x0 0x10>;
#pwm-cells = <3>;
pinctrl-names = "default";
pinctrl-0 = <&pwm0_pin>;
clocks = <&pmucru PCLK_RKPWM_PMU>;
status = "disabled";
};
pinctrl: pinctrl {
......
pwm0 {
pwm0_pin: pwm0-pin {
rockchip,pins =
<4 RK_PC2 1 &pcfg_pull_none>;
};
pwm0_pin_pull_down: pwm0-pin-pull-down {
rockchip,pins =
<4 RK_PC2 1 &pcfg_pull_down>;
};
vop0_pwm_pin: vop0-pwm-pin {
rockchip,pins =
<4 RK_PC2 2 &pcfg_pull_none>;
};
vop1_pwm_pin: vop1-pwm-pin {
rockchip,pins =
<4 RK_PC2 3 &pcfg_pull_none>;
};
};
}
由于eDP
接口PWM0_BL
引脚接RK3399 GPIO4_C2/PWM0/VOP0_PWM/VOP1_PWM
,这里配置GPIO4_C2
引脚功能复用为pwm0_pin
。
具体可以参考GRF_GPIO4C_IOMUX
寄存器配置:
更多PWM
驱动相关内容参考:《Rockchip RK3399 - GPIO&PWM
风扇调试》。
1.2.3 edp_panel
panel
驱动位于drivers/gpu/drm/panel/panel-simple.c
,设备节点的配置参考文档 :
Documentation/devicetree/bindings/display/panel/panel-simple.yaml
:
在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中新增edp_panel
配置,如下:
edp_panel: edp-panel {
compatible = "chunghwa,claa070wp03xg";
status = "okay";
backlight = <&backlight>;
power-supply = <&vcc3v3_s0>;
prepare-delay-ms = <20>;
enable-delay-ms = <20>;
port {
panel_in_edp: endpoint {
remote-endpoint = <&edp_out_panel>;
};
};
};
如果你参考官方提供的linux 5.10
内核,你会发现compatible
配置为panel-simple
,这里我们为啥没有配置为simple-panel
,主要是因为在最新的内核文件panel-simple.c
中不再支持simple-panel
。
由于我们使用的友善7寸高清电容触摸显示屏(型号为HD702E
)分辨率为800x1280
,屏幕大小94.2(H)x150.72(V) (mm) (7-inch diagonal)
,推荐像素时钟频率为66.77MHz
,因此我们搜索panel-simple.c
文件,查找vdisplay = 1280
字符串,可以定位到匹配的时序配置chunghwa_claa070wp03xg_mode
;
static const struct drm_display_mode chunghwa_claa070wp03xg_mode = {
.clock = 66770,
.hdisplay = 800,
.hsync_start = 800 + 49,
.hsync_end = 800 + 49 + 33,
.htotal = 800 + 49 + 33 + 17,
.vdisplay = 1280,
.vsync_start = 1280 + 1,
.vsync_end = 1280 + 1 + 7,
.vtotal = 1280 + 1 + 7 + 15,
.flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
};
static const struct panel_desc chunghwa_claa070wp03xg = {
.modes = &chunghwa_claa070wp03xg_mode,
.num_modes = 1,
.bpc = 6,
.size = {
.width = 94,
.height = 150,
},
.bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,
.bus_flags = DRM_BUS_FLAG_DE_HIGH,
.connector_type = DRM_MODE_CONNECTOR_LVDS,
};
static const struct of_device_id platform_of_match[] = {
[...]
}, {
.compatible = "chunghwa,claa070wp03xg",
.data = &chunghwa_claa070wp03xg,
}, {
[...]
}
1.2.4 gt9xx
HD702E
友善7寸高清电容触摸显示屏搭载的触摸IC
型号为GT9271
。GT9271
驱动位于drivers/input/touchscreen/goodix.c
,设备节点的配置参考文档 :
Documentation/devicetree/bindings/input/touchscreen/goodix.yaml
:
因此需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中新增gt9xx
设备节点,如下:
&i2c4 {
status = "okay";
......
gt9xx: goodix_ts@5d {
compatible = "goodix,gt9271"";
reg = <0x5d>;
interrupt-parent = <&gpio1>;
interrupts = <RK_PC4 IRQ_TYPE_EDGE_FALLING>;
AVDD28-supply = <&vcc3v3_sys>;
VDDIO-supply = <&vcc3v3_sys>;
irq-gpios = <&gpio1 RK_PC4 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>;
status = "okay";
};
}
AVDD28-supply
:AVDD28
引脚的模拟电源供应;
VDDIO-supply
:VDDIO
引脚的GPIO
电源供应;
eDP
接口的GPIO1_C4_TP_INT
连接到RK3399
的GPIO1_C4
引脚,默认高电平,发生触摸后该引脚会发送一个低电平的脉冲信号。因此中断配置为:
interrupt-parent = <&gpio1>;
interrupts = <RK_PC4 IRQ_TYPE_EDGE_FALLING>;
irq-gpios = <&gpio1 RK_PC4 GPIO_ACTIVE_HIGH>;
eDP
接口的GPIO1_B5_TP_RST
连接到RK3399
的GPIO1_B5
引脚,触摸面板的复位引脚。因此reset-gpios
配置为:
reset-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_HIGH>;
1.3 电源配置
eDP
接口使用到得电源有VCC12V0_SYS
、VCC3V3_SYS
、VCC0V9_S3
,对应得设备节点均定义在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
;
1.3.1 VCC12V0_SYS
vcc12v0_sys: vcc12v0-sys {
compatible = "regulator-fixed";
regulator-always-on;
regulator-boot-on;
regulator-max-microvolt = <12000000>;
regulator-min-microvolt = <12000000>;
regulator-name = "vcc12v0_sys";
};
1.3.2 VCC3V3_SYS
vcc3v3_sys: vcc3v3-sys {
compatible = "regulator-fixed";
regulator-name = "vcc3v3_sys";
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
vin-supply = <&vcc12v0_sys>;
};
1.3.2 VCC0V9_S3
/*
* Really, this is supplied by vcc_1v8, and vcc1v8_s3 only
* drives the enable pin, but we can't quite model that.
*/
vcca0v9_s3: vcca0v9-s3 {
compatible = "regulator-fixed";
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <900000>;
regulator-name = "vcca0v9_s3";
vin-supply = <&vcc1v8_s3>;
};
1.4 启用eDP
由于我们之前已经将hdmi
连接在vopb
上,因此在这里我们希望eDP
连接在vopl
上,则需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中为以下节点新增属性:
&display_subsystem {
status = "okay";
};
&vopl {
status = "okay";
};
&vopl_mmu {
status = "okay";
};
&edp {
status = "okay";
force-hpd;
ports {
edp_out: port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
edp_out_panel: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_in_edp>;
};
};
};
};
&edp_in_vopl{
status = "okay";
};
&edp_in_vopb{
status = "disabled";
};
对于Embedded Connection
,一般不需要HPD
功能,需要加上force-hpd
属性。
此外,edp
新增1个output endpoint
,连接到了epd panel
上,因此可以得到1条通路:
edp_out_panel
--->panel_in_edp
。
二、Platform
驱动
2.1 模块入口函数
在drivers/gpu/drm/rockchip/rockchip_drm_drv.c
文件rockchip_drm_init
函数:
static int __init rockchip_drm_init(void)
{
int ret;
if (drm_firmware_drivers_only())
return -ENODEV;
// 1. 根据配置来决定是否添加xxx_xxx_driver到数组rockchip_sub_drivers
num_rockchip_sub_drivers = 0;
ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,CONFIG_ROCKCHIP_ANALOGIX_DP);
......
// 2. 注册多个platform driver
ret = platform_register_drivers(rockchip_sub_drivers,
num_rockchip_sub_drivers);
if (ret)
return ret;
// 3. 注册rockchip_drm_platform_driver
ret = platform_driver_register(&rockchip_drm_platform_driver);
if (ret)
goto err_unreg_drivers;
return 0;
......
}
其中:
ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,CONFIG_ROCKCHIP_ANALOGIX_DP);
会将rockchip_dp_driver
保存到rockchip_sub_drivers
数组中。
并调用platform_register_drivers
遍历rockchip_sub_drivers
数组,多次调用platform_driver_register
注册platform driver
。
2.2 rockchip_dp_driver
rockchip_dp_driver
定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
;
struct platform_driver rockchip_dp_driver = {
.probe = rockchip_dp_probe,
.remove = rockchip_dp_remove,
.driver = {
.name = "rockchip-dp",
.pm = &rockchip_dp_pm_ops,
.of_match_table = rockchip_dp_dt_ids, // 用于设备树匹配
},
};
其中of_match_table
用于设备树匹配,匹配设备树中compatible = "rockchip,rk3399-edp"
的设备节点,即edp
设备节点;
static const struct of_device_id rockchip_dp_dt_ids[] = {
{.compatible = "rockchip,rk3288-dp", .data = &rk3288_dp },
{.compatible = "rockchip,rk3399-edp", .data = &rk3399_edp },
{}
};
2.3 rockchip_dp_probe
在plaform
总线设备驱动模型中,我们知道当内核中有platform
设备和platform
驱动匹配,会调用到platform_driver
里的成员.probe
,在这里就是rockchip_dp_probe
函数;
static const struct rockchip_dp_chip_data rk3399_edp = {
.lcdsel_grf_reg = RK3399_GRF_SOC_CON20,
.lcdsel_big = HIWORD_UPDATE(0, RK3399_EDP_LCDC_SEL),
.lcdsel_lit = HIWORD_UPDATE(RK3399_EDP_LCDC_SEL, RK3399_EDP_LCDC_SEL),
.chip_type = RK3399_EDP,
};
static const struct component_ops rockchip_dp_component_ops = {
.bind = rockchip_dp_bind,
.unbind = rockchip_dp_unbind,
};
static int rockchip_dp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct rockchip_dp_chip_data *dp_data;
struct drm_panel *panel = NULL;
struct rockchip_dp_device *dp;
int ret;
// 获取配置数据,这里是rk3399_edp
dp_data = of_device_get_match_data(dev);
if (!dp_data)
return -ENODEV;
// 基于设备树中的信息,获取connector连接的panel
ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
if (ret < 0)
return ret;
// 动态分配数据结构,并进行成员初始化
dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
dp->dev = dev;
dp->adp = ERR_PTR(-ENODEV);
// 保存Rockchip平台数据
dp->data = dp_data;
dp->plat_data.panel = panel;
// 此处设置为RK3399_EDP
dp->plat_data.dev_type = dp->data->chip_type;
dp->plat_data.power_on_start = rockchip_dp_poweron_start;
dp->plat_data.power_off = rockchip_dp_powerdown;
dp->plat_data.get_modes = rockchip_dp_get_modes;
// Rockchip平台eDP设备探测函数,主要是初始化时钟成员
ret = rockchip_dp_of_probe(dp);
if (ret < 0)
return ret;
// 设置驱动私有数据 pdev->dev.driver_data = dp
platform_set_drvdata(pdev, dp);
// 初始化edp接口
dp->adp = analogix_dp_probe(dev, &dp->plat_data);
if (IS_ERR(dp->adp))
return PTR_ERR(dp->adp);
// 为设备dev向系统注册一个component
ret = component_add(dev, &rockchip_dp_component_ops);
if (ret)
goto err_dp_remove;
return 0;
err_dp_remove:
analogix_dp_remove(dp->adp);
return ret;
}
rockchip_dp_probe
函数的代码虽然看着那么多,实际上主要就做了以下几件事;
- 调用
of_device_get_match_data
根据设备树获取匹配配置数据,这里是rk3399_edp
; - 调用
drm_of_find_panel_or_bridge
根据设备树中的信息,找到可能连接的面板或桥接器; - 调用
devm_kzalloc
在内核堆中动态分配内存,用于存储eDP
设备相关的数据结构rockchip_dp_device
,接下来实对其成员的初始化; - 调用
rockchip_dp_of_probe
,即执行Rockchip eDP
的设备特定探测函数,进一步初始化eDP
设备; - 调用
platform_set_drvdata
设置驱动私有数据pdev->dev.driver_data = dp
; - 调用
component_add
为设备dev
向系统注册一个component
,其中组件可执行的初始化操作被设置为了rockchip_dp_component_ops
;
2.3.1 drm_of_find_panel_or_bridge
drm_of_find_panel_or_bridge
函数定义在drivers/gpu/drm/drm_of.c
;用于在设备树节点中查找与特定端口和端点关联的 面板或桥接器设备。它可以返回与指定的端口和端点关联的 drm_panel
或 drm_bridge
设备。
/**
* drm_of_find_panel_or_bridge - return connected panel or bridge device
* @np: device tree node containing encoder output ports
* @port: port in the device tree node
* @endpoint: endpoint in the device tree node
* @panel: pointer to hold returned drm_panel
* @bridge: pointer to hold returned drm_bridge
*
* Given a DT node's port and endpoint number, find the connected node and
* return either the associated struct drm_panel or drm_bridge device. Either
* @panel or @bridge must not be NULL.
*
* This function is deprecated and should not be used in new drivers. Use
* devm_drm_of_get_bridge() instead.
*
* Returns zero if successful, or one of the standard error codes if it fails.
*/
int drm_of_find_panel_or_bridge(const struct device_node *np, // 传入edp设备节点
int port, int endpoint, // 1 0
struct drm_panel **panel, // 传入&panel
struct drm_bridge **bridge) // null
{
int ret = -EPROBE_DEFER;
struct device_node *remote;
if (!panel && !bridge)
return -EINVAL;
if (panel)
*panel = NULL;
/*
* of_graph_get_remote_node() produces a noisy error message if port
* node isn't found and the absence of the port is a legit case here,
* so at first we silently check whether graph presents in the
* device-tree node.
* 检查设备节点np是否有port或者ports子节点,由于edp设备节点有子节点ports很显然成立
*/
if (!of_graph_is_present(np))
return -ENODEV;
// 首先获取edp设备节点下port=1、endpoint=0的endpoint设备节点,这里匹配到的设备节点为edp_out_panel;然后获取该节点remote-endpoint属性指定的设备节点,即panel_in_edp;
remote = of_graph_get_remote_node(np, port, endpoint);
if (!remote)
return -ENODEV;
// 如果指定了panel,即查找panel
if (panel) {
// 根据remote设备节点获取panel
*panel = of_drm_find_panel(remote);
if (!IS_ERR(*panel))
ret = 0;
else
*panel = NULL;
}
/* No panel found yet, check for a bridge next. */
if (bridge) {
if (ret) {
*bridge = of_drm_find_bridge(remote);
if (*bridge)
ret = 0;
} else {
*bridge = NULL;
}
}
of_node_put(remote);
return ret;
}
of_graph_get_remote_node
在本节最后单独分析,其目的是通过获取edp
设备节点下port=1、endpoint=0
的endpoint
设备节点,这里匹配到的设备节点为edp_out_panel
;然后获取该节点remote-endpoint
属性指定的设备节点,即panel_in_edp
;
从而可以调用of_drm_find_panel
函数根据panel
设备节点获取到对应的panel
对象,of_drm_find_panel
函数定义在drivers/gpu/drm/drm_panel.c
;
/**
* of_drm_find_panel - look up a panel using a device tree node
* @np: device tree node of the panel
*
* Searches the set of registered panels for one that matches the given device
* tree node. If a matching panel is found, return a pointer to it.
*
* Return: A pointer to the panel registered for the specified device tree
* node or an ERR_PTR() if no panel matching the device tree node can be found.
*
* Possible error codes returned by this function:
*
* - EPROBE_DEFER: the panel device has not been probed yet, and the caller
* should retry later
* - ENODEV: the device is not available (status != "okay" or "ok")
*/
struct drm_panel *of_drm_find_panel(const struct device_node *np)
{
struct drm_panel *panel;
if (!of_device_is_available(np))
return ERR_PTR(-ENODEV);
mutex_lock(&panel_lock);
// 遍历链表panel_list,查找设备节点为np的panel
list_for_each_entry(panel, &panel_list, list) {
if (panel->dev->of_node == np) {
mutex_unlock(&panel_lock);
return panel;
}
}
mutex_unlock(&panel_lock);
return ERR_PTR(-EPROBE_DEFER);
}
2.3.2 rockchip_dp_poweron_start
rockchip_dp_poweron_start
函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
:
static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
{
reset_control_assert(dp->rst);
usleep_range(10, 20);
reset_control_deassert(dp->rst);
return 0;
}
static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data)
{
struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);
int ret;
// 准备和使能pclk时钟
ret = clk_prepare_enable(dp->pclk);
if (ret < 0) {
DRM_DEV_ERROR(dp->dev, "failed to enable pclk %d\n", ret);
return ret;
}
// 预初始化
ret = rockchip_dp_pre_init(dp);
if (ret < 0) {
DRM_DEV_ERROR(dp->dev, "failed to dp pre init %d\n", ret);
clk_disable_unprepare(dp->pclk);
return ret;
}
return ret;
}
2.3.3 rockchip_dp_powerdown
rockchip_dp_powerdown
函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
:
static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
{
struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);
// 禁用并关闭pclk时钟
clk_disable_unprepare(dp->pclk);
return 0;
}
2.3.4 rockchip_dp_get_modes
rockchip_dp_get_modes
函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
, 用于获取connector
的显示模式;
static int rockchip_dp_get_modes(struct analogix_dp_plat_data *plat_data,
struct drm_connector *connector)
{
struct drm_display_info *di = &connector->display_info;
/* VOP couldn't output YUV video format for eDP rightly */
u32 mask = DRM_COLOR_FORMAT_YCBCR444 | DRM_COLOR_FORMAT_YCBCR422;
// 如果颜色格式设置了DRM_COLOR_FORMAT_YCBCR444、DRM_COLOR_FORMAT_YCBCR422
if ((di->color_formats & mask)) {
DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n");
di->color_formats &= ~mask; // 屏蔽该颜色模式
di->color_formats |= DRM_COLOR_FORMAT_RGB444;
di->bpc = 8;
}
return 0;
}
2.3.5 rockchip_dp_of_probe
rockchip_dp_get_modes
函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
:
static int rockchip_dp_of_probe(struct rockchip_dp_device *dp)
{
struct device *dev = dp->dev;
struct device_node *np = dev->of_node;
// 根据设备树节点中的rockchip,grf属性获取与GRF相关的寄存器映射 rockchip,grf = <&grf>;
dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(dp->grf)) {
DRM_DEV_ERROR(dev, "failed to get rockchip,grf property\n");
return PTR_ERR(dp->grf);
}
// 获取grf相关的时钟 <&cru PCLK_VIO_GRF>
dp->grfclk = devm_clk_get(dev, "grf");
if (PTR_ERR(dp->grfclk) == -ENOENT) {
dp->grfclk = NULL;
} else if (PTR_ERR(dp->grfclk) == -EPROBE_DEFER) {
return -EPROBE_DEFER;
} else if (IS_ERR(dp->grfclk)) {
DRM_DEV_ERROR(dev, "failed to get grf clock\n");
return PTR_ERR(dp->grfclk);
}
// 获取pclk相关的时钟 <&cru PCLK_EDP_CTRL>
dp->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dp->pclk)) {
DRM_DEV_ERROR(dev, "failed to get pclk property\n");
return PTR_ERR(dp->pclk);
}
// 复位控制 <&cru SRST_P_EDP_CTRL>
dp->rst = devm_reset_control_get(dev, "dp");
if (IS_ERR(dp->rst)) {
DRM_DEV_ERROR(dev, "failed to get dp reset control\n");
return PTR_ERR(dp->rst);
}
return 0;
}
2.4 of_graph_get_remote_node
of_graph_get_remote_node
定义在drivers/of/property.c
,函数第1个传入的是edp
设备节点,第2个参数port
传入1,第3个参数reg
传入0。
这段代码首先获取edp
设备节点下port=1
、endpoint=0
的endpoint
设备节点,这里匹配到的设备节点为edp_out_panel
;
然后获取该节点remote-endpoint
属性指定的设备节点,即panel_in_edp
;
/**
* of_graph_get_remote_node() - get remote parent device_node for given port/endpoint
* @node: pointer to parent device_node containing graph port/endpoint
* @port: identifier (value of reg property) of the parent port node
* @endpoint: identifier (value of reg property) of the endpoint node
*
* Return: Remote device node associated with remote endpoint node linked
* to @node. Use of_node_put() on it when done.
*/
struct device_node *of_graph_get_remote_node(const struct device_node *node, // 传入edp设备节点
u32 port, u32 endpoint) // 1 0
{
struct device_node *endpoint_node, *remote;
// 通过遍历node设备节点的所有端点节点来查找符合指定port=1和endpoint=0的端点节点,这里返回的是edp_out_panel设备节点
endpoint_node = of_graph_get_endpoint_by_regs(node, port, endpoint);
if (!endpoint_node) {
pr_debug("no valid endpoint (%d, %d) for node %pOF\n",
port, endpoint, node);
return NULL;
}
// 首先获取endpoint_node设备节点remote-endpoin属性指定的设备节点,即panel_in_edp设备节点,并向上查找父设备节点,直至找到edp_panel设备节点
remote = of_graph_get_remote_port_parent(endpoint_node);
of_node_put(endpoint_node);
if (!remote) {
pr_debug("no valid remote node\n");
return NULL;
}
// 判断这个设备节点是否处于启用状态 即status = "ok"
if (!of_device_is_available(remote)) {
pr_debug("not available for remote node\n");
of_node_put(remote);
return NULL;
}
return remote;
}
2.4.1 of_graph_get_endpoint_by_regs
of_graph_get_endpoint_by_regs
定义在drivers/of/property.c
,其作用就是通过遍历parent
设备节点的所有端点节点来查找符合指定 port_reg
和 reg
标识符的端点节点;
/**
* of_graph_get_endpoint_by_regs() - get endpoint node of specific identifiers
* @parent: pointer to the parent device node
* @port_reg: identifier (value of reg property) of the parent port node
* @reg: identifier (value of reg property) of the endpoint node
*
* Return: An 'endpoint' node pointer which is identified by reg and at the same
* is the child of a port node identified by port_reg. reg and port_reg are
* ignored when they are -1. Use of_node_put() on the pointer when done.
*/
struct device_node *of_graph_get_endpoint_by_regs(
const struct device_node *parent, int port_reg, int reg) // 传入edp设备节点 1 0
{
struct of_endpoint endpoint;
struct device_node *node = NULL;
// 遍历parent结点下的每个endpoint结点
for_each_endpoint_of_node(parent, node) {
// 解析端点的信息,并将结果存储在endpoint
of_graph_parse_endpoint(node, &endpoint);
// 对比传入的port_reg和reg参数与当前端点节点的属性值,如果匹配则返回该端点节点的指针
if (((port_reg == -1) || (endpoint.port == port_reg)) &&
((reg == -1) || (endpoint.id == reg)))
return node;
}
return NULL;
}
比如我们的edp
节点,当调用of_graph_get_endpoint_by_regs(node,1,0)
首先查找reg=1
的port
,即edp_out
,然后查找reg=0的端点节点,也就是edp_out_panel
设备节点;
edp: edp@ff970000 {
......
ports {
#address-cells = <1>;
#size-cells = <0>;
edp_in: port@0 { # 该节点和port_reg=0匹配, 节点属性reg的值赋值给endpoint.port
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
edp_in_vopb: endpoint@0 {
reg = <0>;
remote-endpoint = <&vopb_out_edp>;
};
edp_in_vopl: endpoint@1 {
reg = <1>;
remote-endpoint = <&vopl_out_edp>;
};
};
edp_out: port@1 { # 该节点和port_reg=1匹配, 节点属性reg的值赋值给endpoint.port
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
edp_out_panel: endpoint@0 { # 属性reg的值赋值给endpoint.id
reg = <0>;
remote-endpoint = <&panel_in_edp>;
};
};
};
};
2.4.2 of_graph_parse_endpoint
of_graph_parse_endpoint
定义在drivers/of/property.c
,函数的作用是解析端点节点node
的属性,并将解析结果存储到 endpoint
中;
/**
* of_graph_parse_endpoint() - parse common endpoint node properties
* @node: pointer to endpoint device_node
* @endpoint: pointer to the OF endpoint data structure
*
* The caller should hold a reference to @node.
*/
int of_graph_parse_endpoint(const struct device_node *node,
struct of_endpoint *endpoint)
{
// endpoint的父结点是port结点
struct device_node *port_node = of_get_parent(node);
WARN_ONCE(!port_node, "%s(): endpoint %pOF has no parent node\n",
__func__, node);
// 填充0
memset(endpoint, 0, sizeof(*endpoint));
// 设置endpoint所属的port节点
endpoint->local_node = node;
/*
* It doesn't matter whether the two calls below succeed.
* If they don't then the default value 0 is used.
* port结点下的reg属性值是endpoint->port值
* endpoint节点reg属性值是endpoint->id值
*/
of_property_read_u32(port_node, "reg", &endpoint->port);
of_property_read_u32(node, "reg", &endpoint->id);
of_node_put(port_node);
return 0;
}
以edp_out_panel
设备节点为例:
edp_out: port@1 {
reg = <1>; # 赋值给endpoint->port
.....
edp_out_panel: endpoint@0 {
reg = <0>; # 赋值给endpoint->id
remote-endpoint = <&panel_in_edp>;
};
}
经过of_graph_parse_endpoint
函数处理后:
endpoint->id = 0
;endpoint->port= 1
;
三、edp
相关数据结构
edp
相关的数据结构分为两部分:
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤
2023-02-27 linux驱动移植-SPI驱动移植(NRF24L01)
2022-02-27 linux驱动移植-中断子系统整体框架