基于STM32MP157调试MIPI-DSI屏幕

2023-11-15

平台:STM32MP157
屏幕:mipi-dsi接口,1024x600
内核版本:linux5-4

本人是第一次调试mipi屏,在157这个平台上遇到的问题有一点多,接下来简单的描述下我的调试经验

一、先配置一下设备树DTB

&ltdc {
    port {
        #address-cells = <1>;
        #size-cells = <0>;

        ltdc_ep1_out: endpoint@1 {
            reg = <1>;
            remote-endpoint = <&dsi_in>;
        };
    };
};

&dsi {
	#address-cells = <1>;
	#size-cells = <0>;
	status = "okay";

	ports {
		#address-cells = <1>;
		#size-cells = <0>;

		port@0 {
			reg = <0>;
			dsi_in: endpoint {
				remote-endpoint = <&ltdc_ep1_out>;
			};
		};


		port@1 {
			reg = <1>;
			dsi_out: endpoint {
				remote-endpoint = <&dsi_panel_in>;
			};
		};
	};
		
	panel@0 {
		compatible = "Hyb-Mipi";
		reg = <0>;
		enable-gpios = <&gpioc 6 GPIO_ACTIVE_HIGH>;	
        reset-gpios = <&gpioe 4 GPIO_ACTIVE_HIGH>;
		status = "okay";
		port {
			dsi_panel_in: endpoint {
				remote-endpoint = <&dsi_out>;
			};
		};
		
	};
};

二、驱动选择

在STM32MP157的源码中,自带了一个文件名为panel-simple.c通用驱动。之前已经在这个文件调通了LCD屏幕,打开文件可以看到,其实这个文件也是可以兼容mipi-dsi的屏幕。可能有些屏幕可以使用这个文件去调试,注意是看看CONFIG_DRM_MIPI_DSI这个选项有无有选择。

static int __init panel_simple_init(void)
{
	int err;

	err = platform_driver_register(&panel_simple_platform_driver);
	if (err < 0)
		return err;

	if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
		err = mipi_dsi_driver_register(&panel_simple_dsi_driver);
		if (err < 0)
			return err;
	}

	return 0;
}
module_init(panel_simple_init);

而我接下来调试的屏幕无法使用这个文件。

找一个类似的驱动去进行修改填充

大家可以在driver/gpu/drm/panel/源码目录下找到一个其他屏幕厂商的驱动代码进行修改。

接下来驱动代码修改:
首先是驱动和设备树适配

static const struct of_device_id hyb_of_match[] = {
	{ .compatible = "Hyb-Mipi", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, hyb_of_match);

static struct mipi_dsi_driver hyb_driver = {
	.probe = hyb_dsi_probe,
	.remove = hyb_dsi_remove,
	.driver = {
		.name = "Hyb-Mipi",
		.of_match_table = hyb_of_match,
	},
};

适配成功后进入hyb_dsi_probe函数,这个函数接口主要是获取一下设备树设置的一些参数,配置一下DSI格式。

static int hyb_dsi_probe(struct mipi_dsi_device *dsi)
{
	struct hyb *ctx;
	int ret;

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

	mipi_dsi_set_drvdata(dsi, ctx);
	ctx->dsi = dsi;

	drm_panel_init(&ctx->panel);
	ctx->panel.dev = &dsi->dev;
	ctx->panel.funcs = &hyb_funcs;
	
	ctx->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_HIGH);
	if (IS_ERR(ctx->reset)) {
		DRM_DEV_ERROR(&dsi->dev, "Couldn't get our reset GPIO\n");
		return PTR_ERR(ctx->reset);
	} else {
		DRM_DEV_ERROR(&dsi->dev, "Success get our reset GPIO\n");
		gpiod_set_value(ctx->reset, 1);
	}

	ctx->enable = devm_gpiod_get(&dsi->dev, "enable", GPIOD_OUT_HIGH);
	if (IS_ERR(ctx->enable)) {
		DRM_DEV_ERROR(&dsi->dev, "Couldn't get our enable GPIO\n");
		return PTR_ERR(ctx->enable);
	} else {
		DRM_DEV_ERROR(&dsi->dev, "Success get our enable GPIO\n");
		gpiod_set_value(ctx->enable, 1);
	}
	ret = drm_panel_add(&ctx->panel);
	if (ret < 0)
		return ret;

	dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
			   MIPI_DSI_CLOCK_NON_CONTINUOUS|MIPI_DSI_MODE_VIDEO_BURST;
	dsi->format = MIPI_DSI_FMT_RGB888;//显示格式
	dsi->lanes = 2;//通道

	return mipi_dsi_attach(dsi);
}

接下来进入hyb_get_modes()函数,这函数主要是配置屏幕参数

static const struct drm_display_mode hyb_default_mode = {
	.clock		= 51200,

	.hdisplay	= 1024,
	.hsync_start	= 1024 + 60,
	.hsync_end	= 1024 + 60 + 60,
	.htotal		= 1024 + 60 + 60 + 90,

	.vdisplay	= 600,
	.vsync_start	= 600 + 12,
	.vsync_end	= 600 + 10 + 5,
	.vtotal		= 600 + 10 + 5 + 2,
	.vrefresh	= 60,

	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
};

static int hyb_get_modes(struct drm_panel *panel)
{
	struct drm_connector *connector = panel->connector;
	struct hyb *ctx = panel_to_hyb(panel);
	struct drm_display_mode *mode;

	mode = drm_mode_duplicate(panel->drm, &hyb_default_mode);
	if (!mode) {
		DRM_DEV_ERROR(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n",
			      hyb_default_mode.hdisplay,
			      hyb_default_mode.vdisplay,
			      hyb_default_mode.vrefresh);
		return -ENOMEM;
	}

	drm_mode_set_name(mode);

	drm_mode_probed_add(connector, mode);

	return 1;
}

接下来就是进入hyb_prepare()函数,MIPI_DSI_MODE_LPM表示默认在 LP 模式下发送初始化序列。这个很重要,因为需要我们发送一个初始化序列到mipi屏上。

static const struct hyb_init_cmd hyb_init_cmds[] = {
	{ .data = { 0x80, 0x8B } },
	{ .data = { 0x81, 0x78 } },
	{ .data = { 0x82, 0x78 } },
	{ .data = { 0x83, 0x78 } },
	{ .data = { 0x84, 0x78 } },
	{ .data = { 0x85, 0x78 } },
	{ .data = { 0x86, 0x78 } },
};


static int hyb_prepare(struct drm_panel *panel)
{
	struct hyb *ctx = panel_to_hyb(panel);
	struct mipi_dsi_device *dsi = ctx->dsi;
	unsigned int i;
	int ret;
	gpiod_set_value(ctx->reset, 1);
	dsi->mode_flags |= MIPI_DSI_MODE_LPM;
	msleep(200);
	/* Select User Command Set table (CMD1) */
	ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xfe, 0x00 }, 2);
	if (ret < 0) {
		DRM_DEV_ERROR(dev, "Failed to Set table (%d)\n", ret);	
	}
	/* Software reset */
	ret = mipi_dsi_dcs_soft_reset(dsi);		/* 0x01 */
	if (ret < 0) {
		DRM_DEV_ERROR(dev, "Failed to do Software Reset (%d)\n", ret);
		//return -1;
	}
	
	usleep_range(5000, 10000);		/* > 5ms */
	//发送初始化序列
	for (i = 0; i < ARRAY_SIZE(hyb_init_cmds); i++) {
		const struct hyb_init_cmd *cmd =
						&hyb_init_cmds[i];

		ret = mipi_dsi_dcs_write_buffer(dsi, cmd->data,
						HYB_INIT_CMD_LEN);
		if (ret < 0) {
			
			return ret;
		}
	}
	ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, 0x0B }, 2);
	if (ret < 0) {
		printk("Failed to set DSI mode\n");	
		//dev_err(dev, "Failed to set DSI mode (%d)\n", ret);
		//goto fail;
	}
	
	ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xB2, 0x10 }, 2);
	if (ret < 0) {
		//dev_err(dev, "Failed to set DSI mode (%d)\n", ret);
		//goto fail;
	}
	u8 buffer[2] = {0xB2, 0x10};
	int value = 0;
	ret = mipi_dsi_generic_read(dsi, &buffer[0], 1, (void *)&value, sizeof(value));
	printk("MIPI_DSI_DEBUG: Reg.%02x Set.%02x Get.%02x Ret.%d Flag.%c\n",
				buffer[0], buffer[1], value, ret, ((buffer[1] == value) ? 'T' : 'F'));
				
	/* Set pixel format */
	ret = mipi_dsi_dcs_set_pixel_format(dsi, 0x77);
	//dev_dbg(dev, "Interface color format set to 0x%x\n", color_format);
	if (ret < 0) {
		printk("Failed to set pixel format\n");
		//dev_err(dev, "Failed to set pixel format (%d)\n", ret);
		//goto fail;
	}

	/* Exit sleep mode */
	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);	/* 0x11 */
	if (ret < 0) {
		printk("Failed to exit sleep mode\n");
		//DRM_DEV_ERROR(&dsi->dev, "Failed to exit sleep mode (%d)\n", ret);
		//goto fail;
	}
	usleep_range(120000, 125000);				/* > 120ms */

	ret = mipi_dsi_dcs_set_display_on(dsi);		/* 0x29 */
	if (ret < 0) {
		printk("Failed to set display ON\n");
		//DRM_DEV_ERROR(&dsi->dev, "Failed to set display ON (%d)\n", ret);
		//goto fail;
	}
	usleep_range(5000, 10000);					/* > 5ms */

	return 0;
}

由于这个屏幕默认是四个通道,但是STM32MP157最多只有两个通道,通过厂商提供的手册,可以通过寄存器进行通道选择的配置。这时候我们在发完初始化序列后,再发配置通道的指令语句,然后,为了验证发送的指令是否改变了MIPI芯片的寄存器,可以进行读取。这样可以保证万无一失,也可以确定是否发送成功,走少很多弯路。

在这里插入图片描述

//写入参数
ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xB2, 0x10 }, 2);
	if (ret < 0) {
	
	}
	u8 buffer[2] = {0xB2, 0x10};
	int value = 0;
	//读取对应的寄存器
	ret = mipi_dsi_generic_read(dsi, &buffer[0], 1, (void *)&value, sizeof(value));
	printk("MIPI_DSI_DEBUG: Reg.%02x Set.%02x Get.%02x\n",
				buffer[0], buffer[1], value);

如果顺利的话,基本上开机就能正常启动屏幕了。

调试过程中遇到的坑

第一个问题:在驱动加载中IO口申请失败

我在这个问题停留了有一段时间,用了各种办法都不知道,然后进入系统,手动申请又可以成功。然后把驱动变成模块进去就可以申请成功了,那这个问题就很明显是驱动加载顺序的问题导致的。
问题解决方案:
在内核源码中include/linux/init.h文件定义了加载优先级

在这里插入图片描述
我看了一下引脚子系统加载的优先级是arch_initcall
而我们经常使用的module_init()是 device_initcall(fn),驱动对应的加载的优先级为6。所以可以在之后在加载。

static int __init panel_simple_init(void)
{
	int err;
	if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
		err = mipi_dsi_driver_register(&hyb_driver);
		if (err < 0)
			return err;
	}

	return 0;
}
module_init(panel_simple_init);
static void __exit panel_simple_exit(void)
{
	if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
		mipi_dsi_driver_unregister(&hyb_driver);

}
module_exit(panel_simple_exit);
//module_mipi_dsi_driver(hyb_driver);把这个给屏蔽了,把module_init()添加进去

第二个问题:一直报stm32-display-dsi 5a000000.dsi: Read payload FIFO is empty

[    3.000353] Hyb-Mipi 5a000000.dsi.0: [drm:hyb_dsi_probe]  Success get our reset GPIO
[    3.007902] Hyb-Mipi 5a000000.dsi.0: [drm:hyb_dsi_probe]  Success get our enable GPIO
[    3.020736] [drm] Supports vblank timestamp caching Rev 2 (21.10.2013).
[    3.025898] [drm] Driver supports precise vblank timestamp query.
[    3.033213] [drm] Initialized stm 1.0.0 20170330 for 5a001000.display-controller on minor 0
[    3.388206] stm32-display-dsi 5a000000.dsi: Read payload FIFO is empty
[    3.409780] stm32-display-dsi 5a000000.dsi: Read payload FIFO is empty
[    3.431353] stm32-display-dsi 5a000000.dsi: Read payload FIFO is empty
[    3.431368] MIPI_DSI_DEBUG: Reg.b2 Set.10 Get.00
[    3.819225] Console: switching to colour frame buffer device 128x37
[    3.880960] stm32-display 5a001000.display-controller: fb0: stmdrmfb frame buffer device

在这里插入图片描述
这个问题是没有在代码中添加dsi->mode_flags |= MIPI_DSI_MODE_LPM;
这个直接关于能否正常发送指令和获取寄存器值。

总结:

在现有的条件下,多去对比类似的驱动编写的模式,从中找到规律,并且结合自身的屏幕问题去修改和补充。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于STM32MP157调试MIPI-DSI屏幕 的相关文章

  • tomcat漏洞之任意文件上传(CVE-2017-12615)

    目录 一 漏洞介绍 二 影响版本 三 原理分析 四 环境搭建 五 利用漏洞上传文件 四个方法 1 在文件名后面添加斜杠 来进行绕过 2 在文件名后面添加 20 来进行绕过 3 在文件名后面添加 DATA来进行绕过 4 上传哥斯特生产的jsp
  • IDEA设置控制台字体大小以及换行不换行(美观)

    一 设置控制台大小 1 左上角 File gt Settings 2 找到 Editor gt Color Scheme gt Console Font 勾选Use 就能根据自己设置字体大小了 二 控制台字体换行 1 如下是换行了 打印的结
  • KVM使用入门

    KVM虚拟化 KVM在使用的时候必须是硬件支持虚拟化的 要确保使用的宿主机或实体机支持硬件虚拟化技术 环境搭建 我在VMware里装了个centos7 把centos7当做宿主机使用 关闭selinux vi etc selinux con

随机推荐