程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

Rockchip RK3399 - DRM vop驱动程序

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC16GB
LPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2023.04
linux6.3
----------------------------------------------------------------------------------------------------------------------------

有关《Rockchip RK3399 - DRM crtc基础知识》我们已经在前面介绍过了,在linux内核中Rockchip VOP driver对应crtc drivercrtc负责连接planeencoder

这里我们介绍一下Rochchip DRM驱动中与vop相关的实现,具体实现文件:

  • drivers/gpu/drm/rockchip/rockchip_drm_vop.c
  • drivers/gpu/drm/rockchip/rockchip_vop_reg.c
  • drivers/gpu/drm/rockchip/rockchip_drm_vop.h;
  • drivers/gpu/drm/rockchip/rockchip_vop_reg.h

一、设备树配置

1.1 vopb设备节点

vopb设备节点为例:

vopb: vop@ff900000 {
		compatible = "rockchip,rk3399-vop-big";
		reg = <0x0 0xff900000 0x0 0x2000>, <0x0 0xff902000 0x0 0x1000>;
		interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;
		assigned-clocks = <&cru ACLK_VOP0>, <&cru HCLK_VOP0>;
		assigned-clock-rates = <400000000>, <100000000>;
		clocks = <&cru ACLK_VOP0>, <&cru DCLK_VOP0>, <&cru HCLK_VOP0>;
		clock-names = "aclk_vop", "dclk_vop", "hclk_vop";
		iommus = <&vopb_mmu>;
		power-domains = <&power RK3399_PD_VOPB>;
		resets = <&cru SRST_A_VOP0>, <&cru SRST_H_VOP0>, <&cru SRST_D_VOP0>;
		reset-names = "axi", "ahb", "dclk";
		status = "disabled";

		vopb_out: port {
				#address-cells = <1>;
				#size-cells = <0>;

				vopb_out_edp: endpoint@0 {
						reg = <0>;
						remote-endpoint = <&edp_in_vopb>;
				};

				vopb_out_mipi: endpoint@1 {
						reg = <1>;
						remote-endpoint = <&mipi_in_vopb>;
				};

				vopb_out_hdmi: endpoint@2 {
						reg = <2>;
						remote-endpoint = <&hdmi_in_vopb>;
				};

				vopb_out_mipi1: endpoint@3 {
						reg = <3>;
						remote-endpoint = <&mipi1_in_vopb>;
				};

				vopb_out_dp: endpoint@4 {
						reg = <4>;
						remote-endpoint = <&dp_in_vopb>;
				};
		};
};

1.2 启用vopb

如果我们希望启用vopb上,则需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts中为以下节点新增属性:

# 使能vopb
&vopb {
        status = "okay";
};

&vopb_mmu {
        status = "okay";
};

&display_subsystem {
         status = "okay";
};

二、Platform驱动

2.1 模块入口函数

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(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
        ......

		// 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(vop_platform_driver, CONFIG_ROCKCHIP_VOP);

会将vop_platform_driver保存到rockchip_sub_drivers数组中。

并调用platform_register_drivers遍历rockchip_sub_drivers数组,多次调用platform_driver_register注册platform driver

2.2 vop_platform_driver

vop_platform_driver定义在drivers/gpu/drm/rockchip/rockchip_vop_reg.c

struct platform_driver vop_platform_driver = {
        .probe = vop_probe,
        .remove = vop_remove,
        .driver = {
                .name = "rockchip-vop",
                .of_match_table = vop_driver_dt_match,
        },
};
2.2.1 of_match_table

其中of_match_table用于设备树匹配,匹配设备树中compatible = "rockchip,rk3399-vop-big"或者compatible = "rockchip,rk3399-vop-lit"的设备节点;

static const struct of_device_id vop_driver_dt_match[] = {
        { .compatible = "rockchip,rk3036-vop",
          .data = &rk3036_vop },
        { .compatible = "rockchip,rk3126-vop",
          .data = &rk3126_vop },
        { .compatible = "rockchip,px30-vop-big",
          .data = &px30_vop_big },
        { .compatible = "rockchip,px30-vop-lit",
          .data = &px30_vop_lit },
        { .compatible = "rockchip,rk3066-vop",
          .data = &rk3066_vop },
        { .compatible = "rockchip,rk3188-vop",
          .data = &rk3188_vop },
        { .compatible = "rockchip,rk3288-vop",
          .data = &rk3288_vop },
        { .compatible = "rockchip,rk3368-vop",
          .data = &rk3368_vop },
        { .compatible = "rockchip,rk3366-vop",
          .data = &rk3366_vop },
        { .compatible = "rockchip,rk3399-vop-big",
          .data = &rk3399_vop_big },
        { .compatible = "rockchip,rk3399-vop-lit",
          .data = &rk3399_vop_lit },
        { .compatible = "rockchip,rk3228-vop",
          .data = &rk3228_vop },
        { .compatible = "rockchip,rk3328-vop",
          .data = &rk3328_vop },
        {},
};
2.2.2 vop_probe

plaftorm总线设备驱动模型中,我们知道当内核中有platform设备和platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是vop_probe函数;

static int vop_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;

		// 未使用设备树 退出
        if (!dev->of_node) {
                DRM_DEV_ERROR(dev, "can't find vop devices\n");
                return -ENODEV;
        }

        return component_add(dev, &vop_component_ops);
}

这里代码很简单,就是为设备pdev->dev向系统注册一个component,其中组件可执行的初始化操作被设置为了vop_component_ops,其定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c

const struct component_ops vop_component_ops = {
        .bind = vop_bind,
        .unbind = vop_unbind,
};

我们需要重点关注bind函数的实现,这个函数内容较多我们单独小节介绍。

三、vop相关数据结构

3.1 struct vop

struct vop定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c,用于表示显示处理器;

// 描述vop win
struct vop_win {
        struct drm_plane base;              // 对应的struct drm_plane数据结构
        const struct vop_win_data *data;    // vop win data
        const struct vop_win_yuv2yuv_data *yuv2yuv_data;
        struct vop *vop;                    // 指向vop
};


// 描述显示处理器vop
struct vop {
        struct drm_crtc crtc;
        struct device *dev;
        struct drm_device *drm_dev;
        bool is_enabled;

        struct completion dsp_hold_completion;
        unsigned int win_enabled;

        /* protected by dev->event_lock */
        struct drm_pending_vblank_event *event;

        struct drm_flip_work fb_unref_work;
        unsigned long pending;

        struct completion line_flag_completion;

        const struct vop_data *data;

        uint32_t *regsbak;
        void __iomem *regs;
        void __iomem *lut_regs;

        /* physical map length of vop register */
        uint32_t len;

        /* one time only one process allowed to config the register */
        spinlock_t reg_lock;
        /* lock vop irq reg */
        spinlock_t irq_lock;
        /* protects crtc enable/disable */
        struct mutex vop_lock;

        unsigned int irq;

        /* vop AHP clk */
        struct clk *hclk;
        /* vop dclk */
        struct clk *dclk;
        /* vop share memory frequency */
        struct clk *aclk;

        /* vop dclk reset */
        struct reset_control *dclk_rst;

        /* optional internal rgb encoder */
        struct rockchip_rgb *rgb;

        struct vop_win win[];
};

其中:

  • crtc:指向一个struct drm_crtc;
  • dev:这是一个指向设备结构体的指针,通常用于表示与vop关联的设备;
  • data :存储与vop相关的数据;
  • drm_dev :指向一个struct drm_device,表示当前drm设备;
  • regs vop相关寄存器起始虚拟地址;
  • len vop相关寄存器所占内存长度,单位字节;
  • regsbak :指向一块内存,用于备份vop相关寄存器;
  • irq :存储vop中断IRQ编号;
  • hclkdclkaclkvop相关的各种时钟;
  • rgb:这是一个用于管理RGB编码器的结构体指针;
  • win:这是一个表示vop win的结构体数组,比如RK399 vop 支持4个图层,因此该该数组有4个元素;

3.2 struct vop_data

struct vop_data定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.h,用于描述与vop相关的数据,主要是一些寄存器信息;

struct vop_data {
        uint32_t version;
        const struct vop_intr *intr;
        const struct vop_common *common;
        const struct vop_misc *misc;
        const struct vop_modeset *modeset;  // 模式配置相关寄存器信息
        const struct vop_output *output;    // 输出配置相关寄存器信息
        const struct vop_afbc *afbc;
        const struct vop_win_yuv2yuv_data *win_yuv2yuv;
        const struct vop_win_data *win;     // vop win配置相关寄存器信息
        unsigned int win_size;   // vop win个数
        unsigned int lut_size;

#define VOP_FEATURE_OUTPUT_RGB10        BIT(0)
#define VOP_FEATURE_INTERNAL_RGB        BIT(1)
        u64 feature;
};

rk3399_vop_big为例,定义在drivers/gpu/drm/rockchip/rockchip_vop_reg.c

static const struct vop_data rk3399_vop_big = {
        .version = VOP_VERSION(3, 5),
        .feature = VOP_FEATURE_OUTPUT_RGB10,
        .intr = &rk3366_vop_intr,
        .common = &rk3399_common,
        .modeset = &rk3288_modeset,   // 模式配置相关寄存器信息
        .output = &rk3399_output,     // 输出配置相关寄存器信息
        .afbc = &rk3399_vop_afbc,
        .misc = &rk3368_misc,
        .win = rk3399_vop_win_data,                   // vop win配置相关寄存器信息
        .win_size = ARRAY_SIZE(rk3399_vop_win_data),  // vop win个数
        .win_yuv2yuv = rk3399_vop_big_win_yuv2yuv_data,
        .lut_size = 1024,
};

其中rk3366_vop_intrrk3288_modesetrk3399_outputrk3399_common等配置了大量的寄存器信息。

3.2.1 rk3288_modeset

rk3288_modeset定义如下:

static const struct vop_modeset rk3288_modeset = {
        .htotal_pw = VOP_REG(RK3288_DSP_HTOTAL_HS_END, 0x1fff1fff, 0), // horizontal total size配置
        .hact_st_end = VOP_REG(RK3288_DSP_HACT_ST_END, 0x1fff1fff, 0),
        .vtotal_pw = VOP_REG(RK3288_DSP_VTOTAL_VS_END, 0x1fff1fff, 0), // vertical total size配置
        .vact_st_end = VOP_REG(RK3288_DSP_VACT_ST_END, 0x1fff1fff, 0),
        .hpost_st_end = VOP_REG(RK3288_POST_DSP_HACT_INFO, 0x1fff1fff, 0),
        .vpost_st_end = VOP_REG(RK3288_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
};

VOP_REG用于定义寄存器信息:

#define _VOP_REG(off, _mask, _shift, _write_mask, _relaxed) \
                { \
                 .offset = off, \
                 .mask = _mask, \
                 .shift = _shift, \
                 .write_mask = _write_mask, \
                 .relaxed = _relaxed, \
                }

#define VOP_REG(off, _mask, _shift) \
                _VOP_REG(off, _mask, _shift, false, true)
3.2.2 rk3399_common

rk3399_common定义如下:

static const struct vop_common rk3399_common = {
        .standby = VOP_REG_SYNC(RK3399_SYS_CTRL, 0x1, 22),
        .gate_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 23),
        .mmu_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 20),
        .dither_down_sel = VOP_REG(RK3399_DSP_CTRL1, 0x1, 4),
        .dither_down_mode = VOP_REG(RK3399_DSP_CTRL1, 0x1, 3),
        .dither_down_en = VOP_REG(RK3399_DSP_CTRL1, 0x1, 2),
        .pre_dither_down = VOP_REG(RK3399_DSP_CTRL1, 0x1, 1),
        .dither_up = VOP_REG(RK3399_DSP_CTRL1, 0x1, 6),
        .dsp_lut_en = VOP_REG(RK3399_DSP_CTRL1, 0x1, 0),
        .update_gamma_lut = VOP_REG(RK3399_DSP_CTRL1, 0x1, 7),
        .lut_buffer_index = VOP_REG(RK3399_DBG_POST_REG1, 0x1, 1),
        .data_blank = VOP_REG(RK3399_DSP_CTRL0, 0x1, 19),
        .dsp_blank = VOP_REG(RK3399_DSP_CTRL0, 0x3, 18),
        .out_mode = VOP_REG(RK3399_DSP_CTRL0, 0xf, 0),
        .cfg_done = VOP_REG_SYNC(RK3399_REG_CFG_DONE, 0x1, 0),
};
3.2.3 rk3399_vop_wn_data

rk3399_vop_wn_data定义如下:

static const struct vop_win_phy rk3399_win01_data = {
        .scl = &rk3288_win_full_scl,
        .data_formats = formats_win_full,
        .nformats = ARRAY_SIZE(formats_win_full),
        .format_modifiers = format_modifiers_win_full_afbc,
        .enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0),
        .format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1),
        .rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12),
        .uv_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 15),
        .x_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 21),   // 水平方向上反转(翻转)使能
        .y_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 22),   // 垂直方向上反转(翻转)使能
        .act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0),
        .dsp_info = VOP_REG(RK3288_WIN0_DSP_INFO, 0x0fff0fff, 0),
        .dsp_st = VOP_REG(RK3288_WIN0_DSP_ST, 0x1fff1fff, 0),
        .yrgb_mst = VOP_REG(RK3288_WIN0_YRGB_MST, 0xffffffff, 0),
        .uv_mst = VOP_REG(RK3288_WIN0_CBR_MST, 0xffffffff, 0),
        .yrgb_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 0),
        .uv_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 16),
        .src_alpha_ctl = VOP_REG(RK3288_WIN0_SRC_ALPHA_CTRL, 0xff, 0),
        .dst_alpha_ctl = VOP_REG(RK3288_WIN0_DST_ALPHA_CTRL, 0xff, 0),
        .channel = VOP_REG(RK3288_WIN0_CTRL2, 0xff, 0),
};

......
    
/*
 * rk3399 vop big windows register layout is same as rk3288, but we
 * have a separate rk3399 win data array here so that we can advertise
 * AFBC on the primary plane.
 */
static const struct vop_win_data rk3399_vop_win_data[] = {
        { .base = 0x00, .phy = &rk3399_win01_data,
          .type = DRM_PLANE_TYPE_PRIMARY },       // primary plane
        { .base = 0x40, .phy = &rk3368_win01_data, 
          .type = DRM_PLANE_TYPE_OVERLAY },       // overlay plane
        { .base = 0x00, .phy = &rk3368_win23_data,
          .type = DRM_PLANE_TYPE_OVERLAY },       // overlay plane
        { .base = 0x50, .phy = &rk3368_win23_data,
          .type = DRM_PLANE_TYPE_CURSOR },        // cursor plane
};

RK3399vop包含4个图层,因此这里存放就是RK3399的4个图层配置相关寄存器的信息。

3.2.4 rk3399_vop_big_win_yuv2yuv_data

rk3399_vop_big_win_yuv2yuv_data定义如下:

static const struct vop_win_yuv2yuv_data rk3399_vop_big_win_yuv2yuv_data[] = {
        { .base = 0x00, .phy = &rk3399_yuv2yuv_win01_data,
          .y2r_en = VOP_REG(RK3399_YUV2YUV_WIN, 0x1, 1) },
        { .base = 0x60, .phy = &rk3399_yuv2yuv_win01_data,
          .y2r_en = VOP_REG(RK3399_YUV2YUV_WIN, 0x1, 9) },
        { .base = 0xC0, .phy = &rk3399_yuv2yuv_win23_data },
        { .base = 0x120, .phy = &rk3399_yuv2yuv_win23_data },

};

3.3 rk3399_output

rk3399_output定义如下:

static const struct vop_output rk3399_output = {
        .dp_dclk_pol = VOP_REG(RK3399_DSP_CTRL1, 0x1, 19),
        .rgb_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 19),
        .hdmi_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 23),
        .edp_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 27),
        .mipi_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 31),
        // 接口引脚极性相关寄存器位
        .dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0x7, 16),
        .rgb_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 16),
        .hdmi_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 20),
        .edp_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 24),
        .mipi_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 28),
        // 接口使能相关寄存器位
        .dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11),       // dp接口输出使能
        .rgb_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 12),      // rgb接口输出使能
        .hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13),     // hdmi接口输出使能
        .edp_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 14),      // edp接口输出使能
        .mipi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 15),     // mipi接口输出使能
        .mipi_dual_channel_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 3),  // mipi1接口输出使能
};

这里以如下代码为例:

#define RK3288_SYS_CTRL                         0x0008

.hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13),

其中:

  • RK3288_SYS_CTRLoffset,表示寄存器偏移,相对vop基址,
  • 0x1mask:表示掩码;
  • 13shift,表示偏移位;

因此,总结下来就是地址为0xff900000+0x0008=0xff900008的寄存器的位13用于hdmi接口的使能。

不幸的是在RK3399 datasheet没有找到与vop相关的任何信息,我猜测Rockchip应该是vop这部分并不对用户开放。

不过我们可以通过drivers/gpu/drm/rockchip/rockchip_vop_reg.h中的宏定义猜测出vop包含了哪些寄存器;

/* rk3399 register definition */
#define RK3399_REG_CFG_DONE                     0x0000
#define RK3399_VERSION_INFO                     0x0004
#define RK3399_SYS_CTRL                         0x0008
#define RK3399_SYS_CTRL1                        0x000c
#define RK3399_DSP_CTRL0                        0x0010
#define RK3399_DSP_CTRL1                        0x0014
#define RK3399_DSP_BG                           0x0018
#define RK3399_MCU_CTRL                         0x001c
#define RK3399_WB_CTRL0                         0x0020
#define RK3399_WB_CTRL1                         0x0024
#define RK3399_WB_YRGB_MST                      0x0028
#define RK3399_WB_CBR_MST                       0x002c
#define RK3399_WIN0_CTRL0                       0x0030
#define RK3399_WIN0_CTRL1                       0x0034
#define RK3399_WIN0_COLOR_KEY                   0x0038
#define RK3399_WIN0_VIR                         0x003c
#define RK3399_WIN0_YRGB_MST                    0x0040
#define RK3399_WIN0_CBR_MST                     0x0044
#define RK3399_WIN0_ACT_INFO                    0x0048
#define RK3399_WIN0_DSP_INFO                    0x004c
#define RK3399_WIN0_DSP_ST                      0x0050
#define RK3399_WIN0_SCL_FACTOR_YRGB             0x0054
#define RK3399_WIN0_SCL_FACTOR_CBR              0x0058
#define RK3399_WIN0_SCL_OFFSET                  0x005c
#define RK3399_WIN0_SRC_ALPHA_CTRL              0x0060
#define RK3399_WIN0_DST_ALPHA_CTRL              0x0064
#define RK3399_WIN0_FADING_CTRL                 0x0068
#define RK3399_WIN0_CTRL2                       0x006c
#define RK3399_WIN1_CTRL0                       0x0070
#define RK3399_WIN1_CTRL1                       0x0074
#define RK3399_WIN1_COLOR_KEY                   0x0078
#define RK3399_WIN1_VIR                         0x007c
#define RK3399_WIN1_YRGB_MST                    0x0080
#define RK3399_WIN1_CBR_MST                     0x0084
#define RK3399_WIN1_ACT_INFO                    0x0088
#define RK3399_WIN1_DSP_INFO                    0x008c
#define RK3399_WIN1_DSP_ST                      0x0090
#define RK3399_WIN1_SCL_FACTOR_YRGB             0x0094
#define RK3399_WIN1_SCL_FACTOR_CBR              0x0098
#define RK3399_WIN1_SCL_OFFSET                  0x009c
#define RK3399_WIN1_SRC_ALPHA_CTRL              0x00a0
#define RK3399_WIN1_DST_ALPHA_CTRL              0x00a4
#define RK3399_WIN1_FADING_CTRL                 0x00a8
#define RK3399_WIN1_CTRL2                       0x00ac
#define RK3399_WIN2_CTRL0                       0x00b0
#define RK3399_WIN2_CTRL1                       0x00b4
#define RK3399_WIN2_VIR0_1                      0x00b8
#define RK3399_WIN2_VIR2_3                      0x00bc
#define RK3399_WIN2_MST0                        0x00c0
#define RK3399_WIN2_DSP_INFO0                   0x00c4
#define RK3399_WIN2_DSP_ST0                     0x00c8
#define RK3399_WIN2_COLOR_KEY                   0x00cc
#define RK3399_WIN2_MST1                        0x00d0
#define RK3399_WIN2_DSP_INFO1                   0x00d4
#define RK3399_WIN2_DSP_ST1                     0x00d8
#define RK3399_WIN2_SRC_ALPHA_CTRL              0x00dc
#define RK3399_WIN2_MST2                        0x00e0
#define RK3399_WIN2_DSP_INFO2                   0x00e4
#define RK3399_WIN2_DSP_ST2                     0x00e8
#define RK3399_WIN2_DST_ALPHA_CTRL              0x00ec
#define RK3399_WIN2_MST3                        0x00f0
#define RK3399_WIN2_DSP_INFO3                   0x00f4
#define RK3399_WIN2_DSP_ST3                     0x00f8
#define RK3399_WIN2_FADING_CTRL                 0x00fc
#define RK3399_WIN3_CTRL0                       0x0100
#define RK3399_WIN3_CTRL1                       0x0104
#define RK3399_WIN3_VIR0_1                      0x0108
#define RK3399_WIN3_VIR2_3                      0x010c
#define RK3399_WIN3_MST0                        0x0110
#define RK3399_WIN3_DSP_INFO0                   0x0114
#define RK3399_WIN3_DSP_ST0                     0x0118
#define RK3399_WIN3_COLOR_KEY                   0x011c
#define RK3399_WIN3_MST1                        0x0120
#define RK3399_WIN3_DSP_INFO1                   0x0124
#define RK3399_WIN3_DSP_ST1                     0x0128
#define RK3399_WIN3_SRC_ALPHA_CTRL              0x012c
#define RK3399_WIN3_MST2                        0x0130
#define RK3399_WIN3_DSP_INFO2                   0x0134
#define RK3399_WIN3_DSP_ST2                     0x0138
#define RK3399_WIN3_DST_ALPHA_CTRL              0x013c
#define RK3399_WIN3_MST3                        0x0140
#define RK3399_WIN3_DSP_INFO3                   0x0144
#define RK3399_WIN3_DSP_ST3                     0x0148
#define RK3399_WIN3_FADING_CTRL                 0x014c
#define RK3399_HWC_CTRL0                        0x0150
#define RK3399_HWC_CTRL1                        0x0154
#define RK3399_HWC_MST                          0x0158
#define RK3399_HWC_DSP_ST                       0x015c
#define RK3399_HWC_SRC_ALPHA_CTRL               0x0160
#define RK3399_HWC_DST_ALPHA_CTRL               0x0164
#define RK3399_HWC_FADING_CTRL                  0x0168
#define RK3399_HWC_RESERVED1                    0x016c
#define RK3399_POST_DSP_HACT_INFO               0x0170
#define RK3399_POST_DSP_VACT_INFO               0x0174
#define RK3399_POST_SCL_FACTOR_YRGB             0x0178
#define RK3399_POST_RESERVED                    0x017c
#define RK3399_POST_SCL_CTRL                    0x0180
#define RK3399_POST_DSP_VACT_INFO_F1            0x0184
#define RK3399_DSP_HTOTAL_HS_END                0x0188
#define RK3399_DSP_HACT_ST_END                  0x018c
#define RK3399_DSP_VTOTAL_VS_END                0x0190
#define RK3399_DSP_VACT_ST_END                  0x0194
#define RK3399_DSP_VS_ST_END_F1                 0x0198
#define RK3399_DSP_VACT_ST_END_F1               0x019c
#define RK3399_PWM_CTRL                         0x01a0
#define RK3399_PWM_PERIOD_HPR                   0x01a4
#define RK3399_PWM_DUTY_LPR                     0x01a8
#define RK3399_PWM_CNT                          0x01ac
#define RK3399_BCSH_COLOR_BAR                   0x01b0
#define RK3399_BCSH_BCS                         0x01b4
#define RK3399_BCSH_H                           0x01b8
#define RK3399_BCSH_CTRL                        0x01bc
#define RK3399_CABC_CTRL0                       0x01c0
#define RK3399_CABC_CTRL1                       0x01c4
#define RK3399_CABC_CTRL2                       0x01c8
#define RK3399_CABC_CTRL3                       0x01cc
#define RK3399_CABC_GAUSS_LINE0_0               0x01d0
#define RK3399_CABC_GAUSS_LINE0_1               0x01d4
#define RK3399_CABC_GAUSS_LINE1_0               0x01d8
#define RK3399_CABC_GAUSS_LINE1_1               0x01dc
#define RK3399_CABC_GAUSS_LINE2_0               0x01e0
#define RK3399_CABC_GAUSS_LINE2_1               0x01e4
#define RK3399_FRC_LOWER01_0                    0x01e8
#define RK3399_FRC_LOWER01_1                    0x01ec
#define RK3399_FRC_LOWER10_0                    0x01f0
#define RK3399_FRC_LOWER10_1                    0x01f4
#define RK3399_FRC_LOWER11_0                    0x01f8
#define RK3399_FRC_LOWER11_1                    0x01fc
#define RK3399_AFBCD0_CTRL                      0x0200
#define RK3399_AFBCD0_HDR_PTR                   0x0204
#define RK3399_AFBCD0_PIC_SIZE                  0x0208
#define RK3399_AFBCD0_STATUS                    0x020c
#define RK3399_AFBCD1_CTRL                      0x0220
#define RK3399_AFBCD1_HDR_PTR                   0x0224
#define RK3399_AFBCD1_PIC_SIZE                  0x0228
#define RK3399_AFBCD1_STATUS                    0x022c
#define RK3399_AFBCD2_CTRL                      0x0240
#define RK3399_AFBCD2_HDR_PTR                   0x0244
#define RK3399_AFBCD2_PIC_SIZE                  0x0248
#define RK3399_AFBCD2_STATUS                    0x024c
#define RK3399_AFBCD3_CTRL                      0x0260
#define RK3399_AFBCD3_HDR_PTR                   0x0264
#define RK3399_AFBCD3_PIC_SIZE                  0x0268
#define RK3399_AFBCD3_STATUS                    0x026c
#define RK3399_INTR_EN0                         0x0280
#define RK3399_INTR_CLEAR0                      0x0284
#define RK3399_INTR_STATUS0                     0x0288
#define RK3399_INTR_RAW_STATUS0                 0x028c
#define RK3399_INTR_EN1                         0x0290
#define RK3399_INTR_CLEAR1                      0x0294
#define RK3399_INTR_STATUS1                     0x0298
#define RK3399_INTR_RAW_STATUS1                 0x029c
#define RK3399_LINE_FLAG                        0x02a0
#define RK3399_VOP_STATUS                       0x02a4
#define RK3399_BLANKING_VALUE                   0x02a8
#define RK3399_MCU_BYPASS_PORT                  0x02ac
#define RK3399_WIN0_DSP_BG                      0x02b0
#define RK3399_WIN1_DSP_BG                      0x02b4
#define RK3399_WIN2_DSP_BG                      0x02b8
#define RK3399_WIN3_DSP_BG                      0x02bc
#define RK3399_YUV2YUV_WIN                      0x02c0
#define RK3399_YUV2YUV_POST                     0x02c4
#define RK3399_AUTO_GATING_EN                   0x02cc
#define RK3399_DBG_POST_REG1                    0x036c
#define RK3399_WIN0_CSC_COE                     0x03a0
#define RK3399_WIN1_CSC_COE                     0x03c0
#define RK3399_WIN2_CSC_COE                     0x03e0
#define RK3399_WIN3_CSC_COE                     0x0400
#define RK3399_HWC_CSC_COE                      0x0420
#define RK3399_BCSH_R2Y_CSC_COE                 0x0440
#define RK3399_BCSH_Y2R_CSC_COE                 0x0460
#define RK3399_POST_YUV2YUV_Y2R_COE             0x0480
#define RK3399_POST_YUV2YUV_3X3_COE             0x04a0
#define RK3399_POST_YUV2YUV_R2Y_COE             0x04c0
#define RK3399_WIN0_YUV2YUV_Y2R                 0x04e0
#define RK3399_WIN0_YUV2YUV_3X3                 0x0500
#define RK3399_WIN0_YUV2YUV_R2Y                 0x0520
#define RK3399_WIN1_YUV2YUV_Y2R                 0x0540
#define RK3399_WIN1_YUV2YUV_3X3                 0x0560
#define RK3399_WIN1_YUV2YUV_R2Y                 0x0580
#define RK3399_WIN2_YUV2YUV_Y2R                 0x05a0
#define RK3399_WIN2_YUV2YUV_3X3                 0x05c0
#define RK3399_WIN2_YUV2YUV_R2Y                 0x05e0
#define RK3399_WIN3_YUV2YUV_Y2R                 0x0600
#define RK3399_WIN3_YUV2YUV_3X3                 0x0620
#define RK3399_WIN3_YUV2YUV_R2Y                 0x0640
#define RK3399_WIN2_LUT_ADDR                    0x1000
#define RK3399_WIN3_LUT_ADDR                    0x1400
#define RK3399_HWC_LUT_ADDR                     0x1800
#define RK3399_CABC_GAMMA_LUT_ADDR              0x1c00
#define RK3399_GAMMA_LUT_ADDR                   0x2000

四、vop_bind

vop_bind函数定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c,涉及到对vop设备节点的解析,咱们以vopb设备节点为例进行分析;

static int vop_bind(struct device *dev, struct device *master, void *data)
{
		// 1. 获取platform_device
        struct platform_device *pdev = to_platform_device(dev);
        const struct vop_data *vop_data;
        struct drm_device *drm_dev = data;
        struct vop *vop;
        struct resource *res;
        int ret, irq;

		// 2. 得到rk3399_vop_big
        vop_data = of_device_get_match_data(dev);
        if (!vop_data)
                return -ENODEV;

        /* Allocate vop struct and its vop_win array,初始化vop win */
        vop = devm_kzalloc(dev, struct_size(vop, win, vop_data->win_size),
                           GFP_KERNEL);
        if (!vop)
                return -ENOMEM;

        vop->dev = dev;
        vop->data = vop_data;
        vop->drm_dev = drm_dev;
		
		// 设置驱动数据为vop
        dev_set_drvdata(dev, vop);

		// 3. 初始化vop win
        vop_win_init(vop);

		// 4. 获取第一个内存资源,即<0x0 0xff900000 0x0 0x2000>; VOP_BIG相关寄存器基地址
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
		
		// 将VOP_BIG相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址
        vop->regs = devm_ioremap_resource(dev, res);
        if (IS_ERR(vop->regs))
                return PTR_ERR(vop->regs);
        vop->len = resource_size(res);

		// 5. 获取第二个内存资源,即<0x0 0xff902000 0x0 0x1000>; LUT相关寄存器基地址
        res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
        if (res) {
                if (vop_data->lut_size != 1024 && vop_data->lut_size != 256) {
                        DRM_DEV_ERROR(dev, "unsupported gamma LUT size %d\n", vop_data->lut_size);
                        return -EINVAL;
                }
				// LUT相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址
                vop->lut_regs = devm_ioremap_resource(dev, res);
                if (IS_ERR(vop->lut_regs))
                        return PTR_ERR(vop->lut_regs);
        }
    
        vop->regsbak = devm_kzalloc(dev, vop->len, GFP_KERNEL);
        if (!vop->regsbak)
                return -ENOMEM;


		// 6. 获取第1个IRQ编号 interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;
        irq = platform_get_irq(pdev, 0);
        if (irq < 0) {
                DRM_DEV_ERROR(dev, "cannot find irq for vop\n");
                return irq;
        }
        vop->irq = (unsigned int)irq;

		// 初始化自旋锁和互斥锁
        spin_lock_init(&vop->reg_lock);
        spin_lock_init(&vop->irq_lock);
        mutex_init(&vop->vop_lock);

		// 7. 创建crtc对象,并和plane关联在一起
        ret = vop_create_crtc(vop);
        if (ret)
                return ret;

		// 电源管理相关
        pm_runtime_enable(&pdev->dev);

    	// 8. vop初始化
        ret = vop_initial(vop);
        if (ret < 0) {
                DRM_DEV_ERROR(&pdev->dev,
                              "cannot initial vop dev - err %d\n", ret);
                goto err_disable_pm_runtime;
        }

		// 9. 申请中断,中断处理函数设置为vop_isr
        ret = devm_request_irq(dev, vop->irq, vop_isr,
                               IRQF_SHARED, dev_name(dev), vop);
        if (ret)
                goto err_disable_pm_runtime;

    	// 未设置
        if (vop->data->feature & VOP_FEATURE_INTERNAL_RGB) {
                vop->rgb = rockchip_rgb_init(dev, &vop->crtc, vop->drm_dev);
                if (IS_ERR(vop->rgb)) {
                        ret = PTR_ERR(vop->rgb);
                        goto err_disable_pm_runtime;
                }
        }
    
		// 10. dma初始化
        rockchip_drm_dma_init_device(drm_dev, dev);

        return 0;

err_disable_pm_runtime:
        pm_runtime_disable(&pdev->dev);
        vop_destroy_crtc(vop);
        return ret;
}

这里我们以设备节点vopb为例,对这段代码进行分析,主要流程如下:

(1) 调用to_platform_device获取platform device,设备节点为vopb

(2) 调用of_device_get_match_data获取与特定设备匹配的数据,这里获取到的数据为rk3399_vop_big;

(3) 调用vop_win_init初始化vop win

(4) 首先调用platform_get_resource(pdev, IORESOURCE_MEM, 0)获取第一个内存资源,即:

<0x0 0xff900000 0x0 0x2000>

0xff900000VOP_BIG相关寄存器基地址;

接着调用devm_ioremap_resource(dev, res)VOP_BIG相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址;

(5) 调用platform_get_resource(pdev, IORESOURCE_MEM, 1)获取第二个内存资源,即:

<0x0 0xff902000 0x0 0x1000>

0xff902000 LUT相关寄存器基地址;

接着调用devm_ioremap_resource(dev, res)LUT相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址;

(6) 调用platform_get_irq(pdev, 0) 获取第1个IRQ编号:

 interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>

(7) 调用vop_create_crtc(vop)初始化crtc对象,并和plane关联在一起;

(8) 调用vop_initial(vop)进行vop初始化;

(9) 调用devm_request_irq(dev, vop->irq, vop_isr, IRQF_SHARED, dev_name(dev), vop)申请中断,中断处理函数设置为vop_isr

(10) 调用rockchip_drm_dma_init_device(drm_dev, dev)进行dma相关的初始化工作;

4.1 vo_win_init

vo_win_init用于初始化vop win,那么什么是vop win,指定就是vop图层,以RK3399vop包含4个图层;

/*
 * Initialize the vop->win array elements.
 */
static void vop_win_init(struct vop *vop)
{
        const struct vop_data *vop_data = vop->data;
        unsigned int i;

    	// 遍历每一个图层
        for (i = 0; i < vop_data->win_size; i++) {
            	// 获取第i个vop win
                struct vop_win *vop_win = &vop->win[i];
            	// 获取第i个vop win配置相关寄存器信息
                const struct vop_win_data *win_data = &vop_data->win[i];

            	// 初始化成员
                vop_win->data = win_data;
                vop_win->vop = vop;

            	// rk3399定义了win_yuv2yuv,因此初始化yuv2yuv_data
                if (vop_data->win_yuv2yuv)
                        vop_win->yuv2yuv_data = &vop_data->win_yuv2yuv[i];
        }
}

4.2 vop_create_crtc

vop_create_crtc用于初始化crtc对象,并和plane关联在一起,这里我们仍然以设备节点vopb为例进行分析;

static int vop_create_crtc(struct vop *vop)
{
    	// 获取vop data
        const struct vop_data *vop_data = vop->data;
    	// 获取device,对应的设备节点为vopb
        struct device *dev = vop->dev;
    	// 获取drm device
        struct drm_device *drm_dev = vop->drm_dev;
        struct drm_plane *primary = NULL, *cursor = NULL, *plane, *tmp;
    	// 获取drm crtc
        struct drm_crtc *crtc = &vop->crtc;
        struct device_node *port;
        int ret;
        int i;

        /*
         * Create drm_plane for primary and cursor planes first, since we need
         * to pass them to drm_crtc_init_with_planes, which sets the
         * "possible_crtcs" to the newly initialized crtc.         
         * 1. 遍历每一个vop win,每个vop win内部包含一个drm_plane,对类型为primary和cursor plane
         * 进行初始化
         */
        for (i = 0; i < vop_data->win_size; i++) {
            	// 获取第i个vop win
                struct vop_win *vop_win = &vop->win[i];
            	// 获取第i个vop win data
                const struct vop_win_data *win_data = vop_win->data;

            	// 只处理primary和cursor plane
                if (win_data->type != DRM_PLANE_TYPE_PRIMARY &&
                    win_data->type != DRM_PLANE_TYPE_CURSOR)
                        continue;
            
				// 进行plane的初始化,其中funcs被设置为vop_plane_funcs
                ret = drm_universal_plane_init(vop->drm_dev,    // drm设备
                                               &vop_win->base,  // 要初始化的plane对象
                                               0,  // 可能的CRTCs的位掩码
                                               &vop_plane_funcs,  // plane的控制函数集合
                                               win_data->phy->data_formats,  // 支持的格式数组(DRM_FORMAT_*)
                                               win_data->phy->nformats,   // formats数组的长度
                                               win_data->phy->format_modifiers, 
                                               win_data->type, // plane的类型
                                               NULL);
                if (ret) {
                        DRM_DEV_ERROR(vop->dev, "failed to init plane %d\n",
                                      ret);
                        goto err_cleanup_planes;
                }

            	// 获取当前plane
                plane = &vop_win->base;
	            // 设置plane的辅助函数helper_private为plane_helper_funcs
                drm_plane_helper_add(plane, &plane_helper_funcs);
            	// 如果vop win data配置了x_mir_en/y_mir_en,则调用drm_plane_create_rotation_property为plane附加rotation property
                vop_plane_add_properties(plane, win_data);
            	// 保存primary plane
                if (plane->type == DRM_PLANE_TYPE_PRIMARY)
                        primary = plane;
            	// 保存cursor plane
                else if (plane->type == DRM_PLANE_TYPE_CURSOR)
                        cursor = plane;
        }
    
		// 2. 使用指定的primary and cursor planes初始化的crtc对象,其中crtc回调函数funcs设置为vop_crtc_funcs
        ret = drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
                                        &vop_crtc_funcs, NULL);
        if (ret)
                goto err_cleanup_planes;

    	// 3. 设置crtc的辅助函数helper_private为vop_crtc_helper_funcs
        drm_crtc_helper_add(crtc, &vop_crtc_helper_funcs);
    	// 进入
        if (vop->lut_regs) {
                drm_mode_crtc_set_gamma_size(crtc, vop_data->lut_size);
                drm_crtc_enable_color_mgmt(crtc, 0, false, vop_data->lut_size);
        }
        /*
         * Create drm_planes for overlay windows with possible_crtcs restricted
         * to the newly created crtc.
         * 4. 遍历每一个vop win,每个vop win内部包含一个drm_plane,对类型为overlay plane
         * 进行初始化
         */
        for (i = 0; i < vop_data->win_size; i++) {
            	// 获取第i个vop win
                struct vop_win *vop_win = &vop->win[i];
	            // 获取第i个vop win data
                const struct vop_win_data *win_data = vop_win->data;
                unsigned long possible_crtcs = drm_crtc_mask(crtc);

            	// 只处理overlay plane
                if (win_data->type != DRM_PLANE_TYPE_OVERLAY)
                        continue;

	            // 进行plane的初始化,其中funcs被设置为vop_plane_funcs
                ret = drm_universal_plane_init(vop->drm_dev, &vop_win->base,
                                               possible_crtcs,
                                               &vop_plane_funcs,
                                               win_data->phy->data_formats,
                                               win_data->phy->nformats,
                                               win_data->phy->format_modifiers,
                                               win_data->type, NULL);
                if (ret) {
                        DRM_DEV_ERROR(vop->dev, "failed to init overlay %d\n",
                                      ret);
                        goto err_cleanup_crtc;
                }
               // 设置plane的辅助函数helper_private为plane_helper_funcs
                drm_plane_helper_add(&vop_win->base, &plane_helper_funcs);
            	// 如果vop win data配置了x_mir_en/y_mir_en,则调用drm_plane_create_rotation_property为plane附加rotation property
                vop_plane_add_properties(&vop_win->base, win_data);
        }


    	// 5. 从vopb节点的子节点列表中查找名为port的子节点,也就是vopb_out节点
        port = of_get_child_by_name(dev->of_node, "port");
        if (!port) {
                DRM_DEV_ERROR(vop->dev, "no port node found in %pOF\n",
                              dev->of_node);
                ret = -ENOENT;
                goto err_cleanup_crtc;
        }

    	// 6. 初始化工作队列
        drm_flip_work_init(&vop->fb_unref_work, "fb_unref",
                           vop_fb_unref_worker);
		
    	// 初始化完成量,
        init_completion(&vop->dsp_hold_completion);
        init_completion(&vop->line_flag_completion);
    	// 设置port节点
        crtc->port = port;

    	// 7. 对crtc进行自刷新相关的辅助函数初始化
        ret = drm_self_refresh_helper_init(crtc);
        if (ret)
                DRM_DEV_DEBUG_KMS(vop->dev,
                        "Failed to init %s with SR helpers %d, ignoring\n",
                        crtc->name, ret);

        return 0;

err_cleanup_crtc:
        drm_crtc_cleanup(crtc);
err_cleanup_planes:
        list_for_each_entry_safe(plane, tmp, &drm_dev->mode_config.plane_list,
                                 head)
                drm_plane_cleanup(plane);
        return ret;

err_cleanup_crtc:
        drm_crtc_cleanup(crtc);
err_cleanup_planes:
        list_for_each_entry_safe(plane, tmp, &drm_dev->mode_config.plane_list,
                                 head)
                drm_plane_cleanup(plane);
        return ret;
}

以下是该函数的主要步骤:

(1) 遍历每一个vop win,每个vop win内部包含一个drm_plane,对类型为primarycursor plane进行初始化;

具体是调用drm_universal_plane_init来初始化drm_plane,并且添加plane辅助函数、设置属性等;

其中funcs被设置为vop_plane_funcs,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c

static const struct drm_plane_funcs vop_plane_funcs = {
        .update_plane   = drm_atomic_helper_update_plane,
        .disable_plane  = drm_atomic_helper_disable_plane,
        .destroy = vop_plane_destroy,
        .reset = drm_atomic_helper_plane_reset,
        .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
        .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
        .format_mod_supported = rockchip_mod_supported,
};

helper_private被设为plane_helper_funcs,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c

static const struct drm_plane_helper_funcs plane_helper_funcs = {
        .atomic_check = vop_plane_atomic_check,
        .atomic_update = vop_plane_atomic_update,
        .atomic_disable = vop_plane_atomic_disable,
        .atomic_async_check = vop_plane_atomic_async_check,
        .atomic_async_update = vop_plane_atomic_async_update,
};

(2) 调用drm_crtc_init_with_planes使用指定的primary and cursor planes初始化的crtc对象,其中crtc回调函数funcs设置为vop_crtc_funcs,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c

static const struct drm_crtc_funcs vop_crtc_funcs = {
        .set_config = drm_atomic_helper_set_config,
        .page_flip = drm_atomic_helper_page_flip,
        .destroy = vop_crtc_destroy,
        .reset = vop_crtc_reset,
        .atomic_duplicate_state = vop_crtc_duplicate_state,
        .atomic_destroy_state = vop_crtc_destroy_state,
        .enable_vblank = vop_crtc_enable_vblank,
        .disable_vblank = vop_crtc_disable_vblank,
        .set_crc_source = vop_crtc_set_crc_source,
        .verify_crc_source = vop_crtc_verify_crc_source,
};

(3) 调用drm_crtc_helper_add设置crtc的辅助函数helper_privatevop_crtc_helper_funcs,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
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-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
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-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(742)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2023-02-25 linux驱动移植-SPI控制器驱动
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示