平台:STM32MP157
屏幕:mipi-dsi接口,1024x600
内核版本:linux5-4
本人是第一次调试mipi屏,在157这个平台上遇到的问题有一点多,接下来简单的描述下我的调试经验
<dc {
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 = <<dc_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;
这个直接关于能否正常发送指令和获取寄存器值。
在现有的条件下,多去对比类似的驱动编写的模式,从中找到规律,并且结合自身的屏幕问题去修改和补充。