petalinux驱动实践26:LCD 驱动

        这一章的 vivado 工程需要使用本教程对应源码中的“LCD_vivado”工程,和 vitis 应用教程中的“33_lcd7_touch”工程有些区别,可以自己对照。

framebuffer 框架 

        lcd 显示原理通俗的来说一般是这样: lcd 通过若干数据线连接到主控芯片 ( 除了数据线之外, 还有时钟线、同步信号线等),主控芯片分配一块内存空间用于存放显示画面数据,通过数据线发送画面数据到 lcd 屏幕显示。返一块内存空间就是帧缓冲 framebuffer Linux 中把 framebuffer 抽象成一个设备,对 lcd 的操作最终被简化成了对  framebuffer 的操作,所以 lcd 驱动在 Linux 内核中的实现也就是 framebuffer 框架。

        framebuffer 框架本质上是一个字符设备,和之前讲的 misc 设备如出一辙,是对字符设备的再封装,并且创建了一个类 /sys/class/graphics 。对于应用层来说,使用 framebuffer 的方法大概是这样的:包含头文件 linux/fb.h 、打开设备文件 /dev/fbn mmap 映射、填充 framebuffer
        framebuffer 框架相关的代码在 /driver/video/fbdev/core/fbmem.c 中,与具体硬件操作相关的代码则是 /driver/video/fbdev 目录下 *fb.c 的这些文件。 fbmem.c 中提供了与用户层对接的操作函数接口以及并和 *fb.c 关联,而 *fb.c 则负责接口函数的具体实现。驱动开发者需要完成的也就是 *fb.c 部分, xilinx 也提供了 xilinxfb.c 。参考这些已经实现的 *fb.c ,大概能推测实现一个 lcd 驱动的步骤大致如下:  

 1)使用 framebuffer_alloc 函数注册一个 fb_info 结构体变量

        fb_info 结构体定义在 /include/linux/fb.h 中,用于描述 fb 设备。我们主要需要关心的是:可变参数 struct fb_var_screeninfo var 、不可发参数 struct fb_fix_screeninfo fix 、操作函数集 struct fb_ops *fbops 。也是我们设置 fb_info 结构 体变量的重点。

2)设置 fb_info 结构体变量,包括实现 fb_fops 操作函数集

        设置固定参数显存字节大小、类型、lcd 行长等。设置可变参数水平分辨率、垂直分辨率、虚拟水平分辨率、虚拟垂直分辨率、像素深度、 RGB 等。其他参数根据需求设置。操作函数根据需要实现。

3)完成硬件相关操作

映射相关寄存器并完成硬件初始化,需要结合具体硬件。

4)使用 register_framebuffer 函数向内核注册 fb_info 结构体变量

这一步也就可以理解为关联*fb.c 和 fbmem.c 中的操作函数接口了。

VDMA

        VMDA 是 xilinx 提供的一个 Ip 核,是 ZYNQMP 设备上显示设备工作的关键。VDMA 好处都有啥?VDMA 可以方便的实现双缓冲或者多缓冲机制。双缓冲机制改善了画面闪烁、撕裂等不良效果。前面我们说了帧缓存,帧缓存中的内容会直接显示到屏幕上,当帧缓存中的数据没有准备好时,屏幕上的图像就会缺损,而双缓冲则是在帧缓存的基础上在加了一个备用缓冲区 back buffer 。数据先在 back buffer 中准备好,再交换前缓冲区和备用缓冲区的显示。也就是原先的前缓冲区变成的后缓冲区,准备下一个显示的数据,而后缓冲区则变成了
前缓冲区,显示图像。
        硬件上 VDMA 通过 AXI_HP 接口访问 DDR 。本质上 VDMA 是一个搬运数据的 IP,为数据今出 DDR 提供了便捷, VDMA 数据接口分读写通道,我们可以通过通道将 AXI-Stream 数据流写入DDR ,也可以将 DDR 中的数据以 AXI-Stream 数据流格式读出。数据进入 DDR 也就达到了我们需要将数据存入帧缓存的目的。VDMA 可以控制 32 个帧缓存,所以能够亲送实现多缓冲。
        VDMA 主要由控制和状态寄存器、数据搬运模块和行缓冲够成。数据进出 DDR 要经过行缓冲,然后由数据搬运模块读写数据。 VDMA 的工作状态可以通过状态寄存器读取。
        VDMA 有以下几种接口:
AXI-lite PS 通过该接口来配置 VDMA
AXI Memory Map write :映射到存储器写;
AXI Memory Map read :映射到存储器读;
AXI Stream Write(S2MM) AXI Stream 规频流写入图像;
AXI Stream Read(MM2S) AXI Stream 规频流读出图像。
        VDMA 也提供了寄存器操作,具体细节可以参考 xilinx 官方提供的说明。 VDMA vivado 中的使用可以参考 cause4 中的触摸屏实验。
        在 Linux 中使用 VDMA 实际上 xilinx 已经实现好了,在文件/drivers/dma/xilinx/xilinx_dma.c中,实现了 zynqmp 支持的 axidma cdma 以及 vdma 。我们可以直接使用这个驱动,作为 lcd 显示的一环来使用 vdma 时,在设备树中添加下面这一段即可:
&amba {
    v_drm_dmaengine_drv: drm-dmaengine-drv {
        compatible = "xlnx,pl-disp";
        dmas = <&axi_vdma_0 0>;
        dma-names = "dma0";
        xlnx,vformat = "RG24";
        xlnx,bridge = <&v_tc_0>;
        #address-cells = <1>;
        #size-cells = <0>;
        dmaengine_port: port@0 {
            reg = <0>;
            dmaengine_crtc: endpoint {
                remote-endpoint = <&lcd_encoder>;
            };
        };
    };
};

DRM 框架

        通过上面截取的设备树 compatible = "xlnx,drm" ,知道 ZYNQMP 上使用了 drm 框架来实现 lcd 显示。 framebuffer 框架无法处理基于  GPU 3D 加速需求,也无法处理多个程序协同访问视频卡的情况。 DRM(Direct Rendering Manager) 应运而生, DRM 独占视频卡的访问权限,由它来负责初始化并维护命令队列、视频卡等硬件资源。要使用 GPU 的程序向 DRM 发 送请求,DRM仲裁避免冲突。同时 DRM 也负责 GPU 切换的问题。通俗的讲  DRM Linux 内核用来管理显示输出、 buffer 分配的。 DRM 由两个部分组成:一 是内核态的子系统,这个子系统对硬件操作进行了一层框架封装。二是提供了一个用户态 libdrm 库,应用程序可以直接操作库里的 API ioctl 或 者是用 framebuffer 提供的接口进行显示相关操作。libdrm 库的具体使用就不展开说了。
        DRM 涵盖了很多以前需要在用户空间处理的问题,如图形执行管理器 GEM、内核模式设置 KMS ,这些都是属于  DRM 子系统。
        GEM(Graphic Execution Manager),主要负责显存的分配。
        KMS(Kernel Mode Setting),主要负责画面更新和设置显示参数 ( 包括分辨率、刷新率等 )
        KMS 特性中又包函这些块: Framebuffer CRTC Planes Encoder Connector
Framebuffer 和 前面 fb 框架中提到的其实是一个意思,驱动和应用都可以访问,使用前需要
格式化,设置分辨率、色彩等选项。它只用于描述显存信息,不负责显存的分配,
        CRTC 是 RGB 信号发生源,阴极射像管上下文,对内连接 frame buffer ,对外连接 encoder 。 会扫描 fb 中的内容并叠加上 planes 中的内容传送到 encoder 。每个 CRTC 至少要有一个 plane。planes 也是一段内存地址,和  framebuffer 的区别是,它给视屏提供了高速通道,可以叠加在 framebuffer 上下,并且  planes 可以存在多个。
        encoder 也就是编码器,他的工作就是把像素数据转换为显示器所需要的信号,不同的显示设备需要不同的电信号,如 DVID VGA MIPI 等。
        connector 通常对应硬件上的连接器如 VGA HDMI 等,同时他还保存与连接器相连的显示设备的相关信息如连接状态、 DPMS 状态等。
        DRM 驱动框架,也就是对上述这些特性的封装,这里只是简单过了一遍, DRM 的具体实现可以说相当复杂不是这里三言两语能说清楚的。好在 xilinx 也为我们提供了一套完整的 drm 驱动实现,在 /driver/gpu/drm/xilinx 文件夹中,我们可以直接使用。使用的方法也就是在设备树中添加相应的节点,具体方法可以参考内核中的文档 \Documentation\devicetree\bindings\drm\xilinx\xilinx_drm.txt。
        我们再回头看上一节中节选的设备树代码,我们之前关注的 axi_vdma_0 这 个节点,他是 plane0 这 个节点下的属性, plane0 是我们上面说到的视频通道,也就是说 VDMA 对应的缓存通道0 对应到了 plane0 视 频通道。

设备树

/include/ "system-conf.dtsi"
/ {
};

/* SD */
&sdhci1 {
	disable-wp;
	no-1-8-v;
};

/* USB */
&dwc3_0 {
	status = "okay";
	dr_mode = "host";
};

&amba {
	zyxclmm_drm {
		compatible = "xlnx,zocl";
		status = "okay";
	};
};

&i2c0 {
	status = "okay";
	clock-frequency = <100000>;
	power-domains = <&zynqmp_firmware PD_I2C_1>;

	alinx_an071@5D {	
		compatible = "goodix,gt9xx";
		reg = <0x5D>;
		interrupt-parent = <&gic>;
		interrupts = <0 91 4>;
	};
};

&v_tc_0 {
	compatible = "xlnx,bridge-v-tc-6.1";
	xlnx,pixels-per-clock = <1>;
};

&axi_dynclk_0 {
	compatible = "digilent,axi-dynclk";
	#clock-cells = <0x0>;			
	linux,phandle = <0xc>;
	phandle = <0xc>;
};

&amba {
	drm-pl-disp-drv {
		compatible = "xlnx,pl-disp";
		dmas = <&axi_vdma_0 0>;
		dma-names = "dma0";
		xlnx,vformat = "RG24";
		xlnx,bridge = <&v_tc_0>;

		port@0 {
			reg = <0>;
			pl_disp_crtc: endpoint {
				remote-endpoint = <&lcd_port>;
			};
		};
	}; 

	ax_lcd_encoder  {
		compatible = "ax-drm-encoder";
		ports {
			#address-cells = <1>;
			#size-cells = <0>;
			encoder_lcd_port: port@0 {
				reg = <0>;
				lcd_port: endpoint {
					remote-endpoint = <&pl_disp_crtc>;
				};
			};
		};
	};
};

LCD 显示驱动

        DRM 驱动相当复杂,涉及的模块也很多,在这里三言两于说清楚是不可能的。要实现 lcd 显示,参考 xlinx 现有的其他显示设备的驱动代码实现是最高效的。这里我们参考 xilinx 的 sdi 显示修改得到了一个 lcd 的显示驱动如下:
// SPDX-License-Identifier: GPL-2.0
/*
 * Xilinx FPGA SDI Tx Subsystem driver.
 *
 * Copyright (c) 2017 Xilinx Pvt., Ltd
 *
 * Contacts: Saurabh Sengar <saurabhs@xilinx.com>
 */

#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drmP.h>
#include <drm/drm_probe_helper.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <video/videomode.h>

/* SDI register offsets */
#define XSDI_TX_RST_CTRL		0x00
#define XSDI_TX_MDL_CTRL		0x04
#define XSDI_TX_GLBL_IER		0x0C
#define XSDI_TX_ISR_STAT		0x10
#define XSDI_TX_IER_STAT		0x14
#define XSDI_TX_ST352_LINE		0x18
#define XSDI_TX_ST352_DATA_CH0		0x1C
#define XSDI_TX_VER			0x3C
#define XSDI_TX_SYS_CFG			0x40
#define XSDI_TX_STS_SB_TDATA		0x60
#define XSDI_TX_AXI4S_STS1		0x68
#define XSDI_TX_AXI4S_STS2		0x6C
#define XSDI_TX_ST352_DATA_DS2		0x70

/* MODULE_CTRL register masks */
#define XSDI_TX_CTRL_M			BIT(7)
#define XSDI_TX_CTRL_INS_CRC		BIT(12)
#define XSDI_TX_CTRL_INS_ST352		BIT(13)
#define XSDI_TX_CTRL_OVR_ST352		BIT(14)
#define XSDI_TX_CTRL_INS_SYNC_BIT	BIT(16)
#define XSDI_TX_CTRL_USE_ANC_IN		BIT(18)
#define XSDI_TX_CTRL_INS_LN		BIT(19)
#define XSDI_TX_CTRL_INS_EDH		BIT(20)
#define XSDI_TX_CTRL_MODE		0x7
#define XSDI_TX_CTRL_MUX		0x7
#define XSDI_TX_CTRL_MODE_SHIFT		4
#define XSDI_TX_CTRL_M_SHIFT		7
#define XSDI_TX_CTRL_MUX_SHIFT		8
#define XSDI_TX_CTRL_ST352_F2_EN_SHIFT	15
#define XSDI_TX_CTRL_420_BIT		BIT(21)
#define XSDI_TX_CTRL_INS_ST352_CHROMA	BIT(23)
#define XSDI_TX_CTRL_USE_DS2_3GA	BIT(24)

/* TX_ST352_LINE register masks */
#define XSDI_TX_ST352_LINE_MASK		GENMASK(10, 0)
#define XSDI_TX_ST352_LINE_F2_SHIFT	16

/* ISR STAT register masks */
#define XSDI_GTTX_RSTDONE_INTR		BIT(0)
#define XSDI_TX_CE_ALIGN_ERR_INTR	BIT(1)
#define XSDI_AXI4S_VID_LOCK_INTR	BIT(8)
#define XSDI_OVERFLOW_INTR		BIT(9)
#define XSDI_UNDERFLOW_INTR		BIT(10)
#define XSDI_IER_EN_MASK		(XSDI_GTTX_RSTDONE_INTR | \
					XSDI_TX_CE_ALIGN_ERR_INTR | \
					XSDI_OVERFLOW_INTR | \
					XSDI_UNDERFLOW_INTR)

/* RST_CTRL_OFFSET masks */
#define XSDI_TX_CTRL_EN			BIT(0)
#define XSDI_TX_BRIDGE_CTRL_EN		BIT(8)
#define XSDI_TX_AXI4S_CTRL_EN		BIT(9)
/* STS_SB_TX_TDATA masks */
#define XSDI_TX_TDATA_GT_RESETDONE	BIT(2)

#define XSDI_TX_MUX_SD_HD_3GA		0
#define	XSDI_TX_MUX_3GB			1
#define	XSDI_TX_MUX_8STREAM_6G_12G	2
#define	XSDI_TX_MUX_4STREAM_6G		3
#define	XSDI_TX_MUX_16STREAM_12G	4

#define SDI_MAX_DATASTREAM		8
#define PIXELS_PER_CLK			2
#define XSDI_CH_SHIFT			29
#define XST352_PROG_PIC			BIT(6)
#define XST352_PROG_TRANS		BIT(7)
#define XST352_2048_SHIFT		BIT(6)
#define XST352_YUV420_MASK		0x03
#define ST352_BYTE3			0x00
#define ST352_BYTE4			0x01
#define GT_TIMEOUT			50
/* SDI modes */
#define XSDI_MODE_HD			0
#define	XSDI_MODE_SD			1
#define	XSDI_MODE_3GA			2
#define	XSDI_MODE_3GB			3
#define	XSDI_MODE_6G			4
#define	XSDI_MODE_12G			5

#define SDI_TIMING_PARAMS_SIZE		48

/**
 * enum payload_line_1 - Payload Ids Line 1 number
 * @PAYLD_LN1_HD_3_6_12G:	line 1 HD,3G,6G or 12G mode value
 * @PAYLD_LN1_SDPAL:		line 1 SD PAL mode value
 * @PAYLD_LN1_SDNTSC:		line 1 SD NTSC mode value
 */
enum payload_line_1 {
	PAYLD_LN1_HD_3_6_12G = 10,
	PAYLD_LN1_SDPAL = 9,
	PAYLD_LN1_SDNTSC = 13
};

/**
 * enum payload_line_2 - Payload Ids Line 2 number
 * @PAYLD_LN2_HD_3_6_12G:	line 2 HD,3G,6G or 12G mode value
 * @PAYLD_LN2_SDPAL:		line 2 SD PAL mode value
 * @PAYLD_LN2_SDNTSC:		line 2 SD NTSC mode value
 */
enum payload_line_2 {
	PAYLD_LN2_HD_3_6_12G = 572,
	PAYLD_LN2_SDPAL = 322,
	PAYLD_LN2_SDNTSC = 276
};

/**
 * struct xlnx_sdi - Core configuration SDI Tx subsystem device structure
 * @encoder: DRM encoder structure
 * @connector: DRM connector structure
 * @dev: device structure
 * @base: Base address of SDI subsystem
 * @mode_flags: SDI operation mode related flags
 * @wait_event: wait event
 * @event_received: wait event status
 * @enable_st352_chroma: Able to send ST352 packets in Chroma stream.
 * @enable_anc_data: Enable/Disable Ancillary Data insertion for Audio
 * @sdi_mode: configurable SDI mode parameter, supported values are:
 *		0 - HD
 *		1 - SD
 *		2 - 3GA
 *		3 - 3GB
 *		4 - 6G
 *		5 - 12G
 * @sdi_mod_prop_val: configurable SDI mode parameter value
 * @sdi_data_strm: configurable SDI data stream parameter
 * @sdi_data_strm_prop_val: configurable number of SDI data streams
 *			    value currently supported are 2, 4 and 8
 * @sdi_420_in: Specifying input bus color format parameter to SDI
 * @sdi_420_in_val: 1 for yuv420 and 0 for yuv422
 * @sdi_420_out: configurable SDI out color format parameter
 * @sdi_420_out_val: 1 for yuv420 and 0 for yuv422
 * @is_frac_prop: configurable SDI fractional fps parameter
 * @is_frac_prop_val: configurable SDI fractional fps parameter value
 * @bridge: bridge structure
 * @height_out: configurable bridge output height parameter
 * @height_out_prop_val: configurable bridge output height parameter value
 * @width_out: configurable bridge output width parameter
 * @width_out_prop_val: configurable bridge output width parameter value
 * @in_fmt: configurable bridge input media format
 * @in_fmt_prop_val: configurable media bus format value
 * @out_fmt: configurable bridge output media format
 * @out_fmt_prop_val: configurable media bus format value
 * @en_st352_c_prop: configurable ST352 payload on Chroma stream parameter
 * @en_st352_c_val: configurable ST352 payload on Chroma parameter value
 * @use_ds2_3ga_prop: Use DS2 instead of DS3 in 3GA mode parameter
 * @use_ds2_3ga_val: Use DS2 instead of DS3 in 3GA mode parameter value
 * @video_mode: current display mode
 * @axi_clk: AXI Lite interface clock
 * @sditx_clk: SDI Tx Clock
 * @vidin_clk: Video Clock
 */
struct xlnx_sdi {
	struct drm_encoder encoder;
	struct drm_connector connector;
	struct device *dev;
	void __iomem *base;
	u32 mode_flags;
	wait_queue_head_t wait_event;
	bool event_received;
	bool enable_st352_chroma;
	bool enable_anc_data;
	struct drm_property *sdi_mode;
	u32 sdi_mod_prop_val;
	struct drm_property *sdi_data_strm;
	u32 sdi_data_strm_prop_val;
	struct drm_property *sdi_420_in;
	bool sdi_420_in_val;
	struct drm_property *sdi_420_out;
	bool sdi_420_out_val;
	struct drm_property *is_frac_prop;
	bool is_frac_prop_val;
	struct xlnx_bridge *bridge;
	struct drm_property *height_out;
	u32 height_out_prop_val;
	struct drm_property *width_out;
	u32 width_out_prop_val;
	struct drm_property *in_fmt;
	u32 in_fmt_prop_val;
	struct drm_property *out_fmt;
	u32 out_fmt_prop_val;
	struct drm_property *en_st352_c_prop;
	bool en_st352_c_val;
	struct drm_property *use_ds2_3ga_prop;
	bool use_ds2_3ga_val;
	struct drm_display_mode video_mode;
	struct clk *axi_clk;
	struct clk *sditx_clk;
	struct clk *vidin_clk;
};

#define connector_to_sdi(c) container_of(c, struct xlnx_sdi, connector)
#define encoder_to_sdi(e) container_of(e, struct xlnx_sdi, encoder)

static const struct drm_display_mode alinx_lcd_001_mode = {
    .clock = 33260,
    .hdisplay = 800,
    .hsync_start = 800 + 40,
    .hsync_end = 800 + 40 + 128,
    .htotal = 800 + 40 + 128 + 88,
    .vdisplay = 480,
    .vsync_start = 480 + 10,
    .vsync_end = 480 + 10 + 2,
    .vtotal = 480 + 10 + 2 + 33,
    .vrefresh = 60,
    .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
    .type = 0,
    .name = "800x480",
};

static int xlnx_sdi_atomic_set_property(struct drm_connector *connector,
			     struct drm_connector_state *state,
			     struct drm_property *property, uint64_t val)
{
	struct xlnx_sdi *sdi = connector_to_sdi(connector);

	if (property == sdi->sdi_mode)
		sdi->sdi_mod_prop_val = (unsigned int)val;
	else if (property == sdi->sdi_data_strm)
		sdi->sdi_data_strm_prop_val = (unsigned int)val;
	else if (property == sdi->sdi_420_in)
		sdi->sdi_420_in_val = val;
	else if (property == sdi->sdi_420_out)
		sdi->sdi_420_out_val = val;
	else if (property == sdi->is_frac_prop)
		sdi->is_frac_prop_val = !!val;
	else if (property == sdi->height_out)
		sdi->height_out_prop_val = (unsigned int)val;
	else if (property == sdi->width_out)
		sdi->width_out_prop_val = (unsigned int)val;
	else if (property == sdi->in_fmt)
		sdi->in_fmt_prop_val = (unsigned int)val;
	else if (property == sdi->out_fmt)
		sdi->out_fmt_prop_val = (unsigned int)val;
	else if (property == sdi->en_st352_c_prop)
		sdi->en_st352_c_val = !!val;
	else if (property == sdi->use_ds2_3ga_prop)
		sdi->use_ds2_3ga_val = !!val;
	else
		return -EINVAL;
	return 0;
}

static int xlnx_sdi_atomic_get_property(struct drm_connector *connector,
			     const struct drm_connector_state *state,
			     struct drm_property *property, uint64_t *val)
{
	struct xlnx_sdi *sdi = connector_to_sdi(connector);

	if (property == sdi->sdi_mode)
		*val = sdi->sdi_mod_prop_val;
	else if (property == sdi->sdi_data_strm)
		*val =  sdi->sdi_data_strm_prop_val;
	else if (property == sdi->sdi_420_in)
		*val = sdi->sdi_420_in_val;
	else if (property == sdi->sdi_420_out)
		*val = sdi->sdi_420_out_val;
	else if (property == sdi->is_frac_prop)
		*val =  sdi->is_frac_prop_val;
	else if (property == sdi->height_out)
		*val = sdi->height_out_prop_val;
	else if (property == sdi->width_out)
		*val = sdi->width_out_prop_val;
	else if (property == sdi->in_fmt)
		*val = sdi->in_fmt_prop_val;
	else if (property == sdi->out_fmt)
		*val = sdi->out_fmt_prop_val;
	else if (property == sdi->en_st352_c_prop)
		*val =  sdi->en_st352_c_val;
	else if (property == sdi->use_ds2_3ga_prop)
		*val =  sdi->use_ds2_3ga_val;
	else
		return -EINVAL;

	return 0;
}

static int xlnx_sdi_drm_add_modes(struct drm_connector *connector)
{
    int num_modes = 0;
    struct drm_display_mode *mode;
    struct drm_device *dev = connector->dev;

    mode = drm_mode_duplicate(dev, &alinx_lcd_001_mode);
    drm_mode_probed_add(connector, mode);
    num_modes++;

    return num_modes;
}

static enum drm_connector_status xlnx_sdi_detect(struct drm_connector *connector, bool force)
{
	return connector_status_connected;
}

static void xlnx_sdi_connector_destroy(struct drm_connector *connector)
{
	drm_connector_unregister(connector);
	drm_connector_cleanup(connector);
	connector->dev = NULL;
}

static const struct drm_connector_funcs xlnx_sdi_connector_funcs = {
	.detect = xlnx_sdi_detect,
	.fill_modes = drm_helper_probe_single_connector_modes,
	.destroy = xlnx_sdi_connector_destroy,
	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
	.reset = drm_atomic_helper_connector_reset,
	.atomic_set_property = xlnx_sdi_atomic_set_property,
	.atomic_get_property = xlnx_sdi_atomic_get_property,
};

static struct drm_encoder *
xlnx_sdi_best_encoder(struct drm_connector *connector)
{
	return &(connector_to_sdi(connector)->encoder);
}

static int xlnx_sdi_get_modes(struct drm_connector *connector)
{
	return xlnx_sdi_drm_add_modes(connector);
}

static struct drm_connector_helper_funcs xlnx_sdi_connector_helper_funcs = {
	.get_modes = xlnx_sdi_get_modes,
	.best_encoder = xlnx_sdi_best_encoder,
};

static void xlnx_sdi_drm_connector_create_property(struct drm_connector *base_connector)
{
	struct drm_device *dev = base_connector->dev;
	struct xlnx_sdi *sdi  = connector_to_sdi(base_connector);

	sdi->is_frac_prop = drm_property_create_bool(dev, 0, "is_frac");
	sdi->sdi_mode = drm_property_create_range(dev, 0,
						  "sdi_mode", 0, 5);
	sdi->sdi_data_strm = drm_property_create_range(dev, 0,
						       "sdi_data_stream", 2, 8);
	sdi->sdi_420_in = drm_property_create_bool(dev, 0, "sdi_420_in");
	sdi->sdi_420_out = drm_property_create_bool(dev, 0, "sdi_420_out");
	sdi->height_out = drm_property_create_range(dev, 0,
						    "height_out", 2, 4096);
	sdi->width_out = drm_property_create_range(dev, 0,
						   "width_out", 2, 4096);
	sdi->in_fmt = drm_property_create_range(dev, 0,
						"in_fmt", 0, 16384);
	sdi->out_fmt = drm_property_create_range(dev, 0,
						 "out_fmt", 0, 16384);
	if (sdi->enable_st352_chroma) {
		sdi->en_st352_c_prop = drm_property_create_bool(dev, 0,
								"en_st352_c");
		sdi->use_ds2_3ga_prop = drm_property_create_bool(dev, 0,
								 "use_ds2_3ga");
	}
}

static void xlnx_sdi_drm_connector_attach_property(struct drm_connector *base_connector)
{
	struct xlnx_sdi *sdi = connector_to_sdi(base_connector);
	struct drm_mode_object *obj = &base_connector->base;

	if (sdi->sdi_mode)
		drm_object_attach_property(obj, sdi->sdi_mode, 0);

	if (sdi->sdi_data_strm)
		drm_object_attach_property(obj, sdi->sdi_data_strm, 0);

	if (sdi->sdi_420_in)
		drm_object_attach_property(obj, sdi->sdi_420_in, 0);

	if (sdi->sdi_420_out)
		drm_object_attach_property(obj, sdi->sdi_420_out, 0);

	if (sdi->is_frac_prop)
		drm_object_attach_property(obj, sdi->is_frac_prop, 0);

	if (sdi->height_out)
		drm_object_attach_property(obj, sdi->height_out, 0);

	if (sdi->width_out)
		drm_object_attach_property(obj, sdi->width_out, 0);

	if (sdi->in_fmt)
		drm_object_attach_property(obj, sdi->in_fmt, 0);

	if (sdi->out_fmt)
		drm_object_attach_property(obj, sdi->out_fmt, 0);

	if (sdi->en_st352_c_prop)
		drm_object_attach_property(obj, sdi->en_st352_c_prop, 0);

	if (sdi->use_ds2_3ga_prop)
		drm_object_attach_property(obj, sdi->use_ds2_3ga_prop, 0);
}

static int xlnx_sdi_create_connector(struct drm_encoder *encoder)
{
	struct xlnx_sdi *sdi = encoder_to_sdi(encoder);
	struct drm_connector *connector = &sdi->connector;
	int ret;

	connector->interlace_allowed = true;
	connector->doublescan_allowed = true;

	ret = drm_connector_init(encoder->dev, connector,
				 &xlnx_sdi_connector_funcs,
				 DRM_MODE_CONNECTOR_Unknown);
	if (ret) {
		dev_err(sdi->dev, "Failed to initialize connector with drm\n");
		return ret;
	}

	drm_connector_helper_add(connector, &xlnx_sdi_connector_helper_funcs);
	drm_connector_register(connector);
	drm_connector_attach_encoder(connector, encoder);
	xlnx_sdi_drm_connector_create_property(connector);
	xlnx_sdi_drm_connector_attach_property(connector);

	return 0;
}

static void xlnx_sdi_set_display_enable(struct xlnx_sdi *sdi)
{

}

static void xlnx_sdi_encoder_atomic_mode_set(struct drm_encoder *encoder,
					     struct drm_crtc_state *crtc_state,
				  struct drm_connector_state *connector_state)
{
	struct xlnx_sdi *sdi = encoder_to_sdi(encoder);

	sdi->video_mode = alinx_lcd_001_mode;
}

static void xlnx_sdi_commit(struct drm_encoder *encoder)
{
	struct xlnx_sdi *sdi = encoder_to_sdi(encoder);
	long ret;

	dev_dbg(sdi->dev, "%s\n", __func__);
	sdi->event_received = false;
}

static void xlnx_sdi_disable(struct drm_encoder *encoder)
{
	
}

static const struct drm_encoder_helper_funcs xlnx_sdi_encoder_helper_funcs = {
	.atomic_mode_set	= xlnx_sdi_encoder_atomic_mode_set,
	.enable			= xlnx_sdi_commit,
	.disable		= xlnx_sdi_disable,
};

static const struct drm_encoder_funcs xlnx_sdi_encoder_funcs = {
	.destroy = drm_encoder_cleanup,
};

static int xlnx_sdi_bind(struct device *dev, struct device *master,
			 void *data)
{
	struct xlnx_sdi *sdi = dev_get_drvdata(dev);
	struct drm_encoder *encoder = &sdi->encoder;
	struct drm_device *drm_dev = data;
	int ret;

	encoder->possible_crtcs = 1;

	drm_encoder_init(drm_dev, encoder, &xlnx_sdi_encoder_funcs,
			 DRM_MODE_ENCODER_TMDS, NULL);

	drm_encoder_helper_add(encoder, &xlnx_sdi_encoder_helper_funcs);

	ret = xlnx_sdi_create_connector(encoder);
	if (ret) {
		dev_err(sdi->dev, "fail creating connector, ret = %d\n", ret);
		drm_encoder_cleanup(encoder);
	}
	return ret;
}

static void xlnx_sdi_unbind(struct device *dev, struct device *master,
			    void *data)
{
	struct xlnx_sdi *sdi = dev_get_drvdata(dev);

	drm_encoder_cleanup(&sdi->encoder);
	drm_connector_cleanup(&sdi->connector);
}

static const struct component_ops xlnx_sdi_component_ops = {
	.bind	= xlnx_sdi_bind,
	.unbind	= xlnx_sdi_unbind,
};

static int xlnx_sdi_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct resource *res;
	struct xlnx_sdi *sdi;
	struct device_node *vpss_node;
	int ret, irq;
	struct device_node *ports, *port;
	u32 nports = 0, portmask = 0;

	sdi = devm_kzalloc(dev, sizeof(*sdi), GFP_KERNEL);
	if (!sdi)
		return -ENOMEM;

	sdi->dev = dev;

	platform_set_drvdata(pdev, sdi);


	/* in case all "port" nodes are grouped under a "ports" node */
	ports = of_get_child_by_name(sdi->dev->of_node, "ports");
	if (!ports) {
		dev_dbg(dev, "Searching for port nodes in device node.\n");
		ports = sdi->dev->of_node;
	}

	for_each_child_of_node(ports, port) {
		struct device_node *endpoint;
		u32 index;

		if (!port->name || of_node_cmp(port->name, "port")) {
			dev_dbg(dev, "port name is null or node name is not port!\n");
			continue;
		}

		endpoint = of_get_next_child(port, NULL);
		if (!endpoint) {
			dev_err(dev, "No remote port at %s\n", port->name);
			of_node_put(endpoint);
			ret = -EINVAL;
			goto err_disable_vidin_clk;
		}

		of_node_put(endpoint);

		ret = of_property_read_u32(port, "reg", &index);
		if (ret) {
			dev_err(dev, "reg property not present - %d\n", ret);
			goto err_disable_vidin_clk;
		}

		portmask |= (1 << index);

		nports++;
	}


	/* initialize the wait queue for GT reset event */
	init_waitqueue_head(&sdi->wait_event);

	/* video mode properties needed by audio driver are shared to audio
	 * driver through a pointer in platform data. This will be used in
	 * audio driver. The solution may be needed to modify/extend to avoid
	 * probable error scenarios
	 */

	sdi->video_mode = alinx_lcd_001_mode;

	
	pdev->dev.platform_data = &sdi->video_mode;

	ret = component_add(dev, &xlnx_sdi_component_ops);
	if (ret < 0)
		goto err_disable_vidin_clk;

	dev_info(sdi->dev, "alinx lcd probed\n");

	return ret;

err_disable_vidin_clk:
	
err_disable_sditx_clk:
	
err_disable_axi_clk:
	

	return ret;
}

static int xlnx_sdi_remove(struct platform_device *pdev)
{
	struct xlnx_sdi *sdi = platform_get_drvdata(pdev);

	component_del(&pdev->dev, &xlnx_sdi_component_ops);
	clk_disable_unprepare(sdi->vidin_clk);
	clk_disable_unprepare(sdi->sditx_clk);
	clk_disable_unprepare(sdi->axi_clk);

	return 0;
}

static const struct of_device_id xlnx_sdi_of_match[] = {
	{ .compatible = "ax-drm-encoder"},
	{ }
};
MODULE_DEVICE_TABLE(of, xlnx_sdi_of_match);

static struct platform_driver sdi_tx_driver = {
	.probe = xlnx_sdi_probe,
	.remove = xlnx_sdi_remove,
	.driver = {
		.name = "alinx_lcd",
		.of_match_table = xlnx_sdi_of_match,
	},
};

module_platform_driver(sdi_tx_driver);

MODULE_AUTHOR("alinx");
MODULE_DESCRIPTION("alinx lcd");
MODULE_LICENSE("GPL v2");

        从 probe 函 数入手看,先抛去前面的设置不看, 381 行调用 compoent_add 函数添加了一个操作函数, alinx_lcd_bind 以及 alinx_lcd_unbind
        在 alinx_lcd_bind 函 数中,可以明显的看到这个驱动实际上就是添加了一个 encoder 和connector,也就是说 lcd 显示器在整个 drm 系统中,是充当的 connector 和 encoder 的角色。上一节说到的 vdma 则是对应到 crtc、fb。

运行测试 (略)

小结

### PetaLinux 中配置和使用显示设备 在嵌入式 Linux 开发过程中,配置和使用显示设备是一个重要的环节。为了使目标板能够正常驱动显示器并呈现图像,需要完成一系列特定的设置。 #### 配置内核支持 确保所使用的 Linux 内核已启用必要的图形子系统模块和支持硬件加速的功能。这通常涉及到修改 `.config` 文件来激活相应的选项: ```bash make menuconfig ``` 导航至 `Device Drivers -> Graphics support` 并选中所需的帧缓冲器 (Framebuffer) 或 DRM/KMS 支持[^1]。 #### 安装图形库与工具链 除了基础的内核支持外,还需要安装额外的应用程序和服务以便于管理和控制显示屏。例如 X Window System、DirectFB 或者 Wayland 等窗口管理器可以被集成进来;同时也要考虑加入如 Qt、GTK+ 这样的 GUI 库用于构建应用程序界面。 通过 Petalinux 的项目结构,在 `project-spec/meta-user/recipes-graphics` 下创建自定义层以添加这些组件: ```bash petalinux-config -c rootfs ``` 在此菜单中可以选择要包含的各种软件包[^2]。 #### 设备树节点调整 对于 ARM 架构的目标平台来说,正确描述连接到 SoC 上的 LCD 控制器和其他外围接口至关重要。编辑对应的 DTS(Device Tree Source) 文件,指定分辨率、刷新率以及其他参数,从而让操作系统知道如何初始化该类资源。 假设有一个 HDMI 接口,则可以在设备树里这样声明它: ```dts &hdmi { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hdmii>; }; ``` 以上代码片段表明启用了 HDMI 功能,并指定了引脚配置[^3]。 #### 测试显示效果 最后一步就是验证整个流程是否成功。可以通过烧录生成好的 SD 卡映像或将固件刷写到 NAND Flash 后启动开发板来进行实际测试。如果一切顺利的话,应该可以看到预期的画面输出。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千册

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值