RK3288 双屏异显

RK3288

Posted by LXG on January 22, 2022

安卓系统双屏异显技术的实现

硬件层面

SOC 内部需要集成两个 LCDC 控制器

dual_screen

驱动层面

dual_screen_driver

  • 保证两个屏幕单独作为主屏的时候都可以正常显示,这样子保证了两个屏幕的屏参都是正确的,而且两个屏幕的物理连接都正常

  • 双屏显示控制部分的实现:默认情况下 rk_screen.c 驱动代码里只会去解析dts里主屏的屏参,在里面加入代码使得根据id同时去解析副屏的屏参。 然后LCDC0的驱动 rk32_lvds.c 以及 LCDC1 的驱动 rk32_dp.c 同时去获取屏幕的相关参数

  • 双屏显示数据部分的实现:已知android系统抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。FrameBuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过FrameBuffer的读写直接对显存进行操作。 用户可以将FrameBuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。 这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节,这些都是由FrameBuffer设备驱动来完成的。 rk系统里面framebuffer的驱动为rk_fb.c。主屏为/dev/graphics/fb0, 副屏为/dev/graphics/fb4, 上层通过打开这两个节点返回文件描述符,通过文件描述符写入两个屏幕的数据,然后DMA,把缓冲区里的数据传给屏幕。 默认情况下rk_fb.c只会去分配一块缓冲区给主屏用,需要修改代码,根据副屏的屏参分配缓冲区给副屏用,大小为长宽每个像素的位数。

rk_screen.c

./kernel/drivers/video/rockchip/screen/rk_screen.c


/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/module.h>
#include <linux/rk_fb.h>
#include <linux/device.h>
#include "lcd.h"
#include "../hdmi/rockchip-hdmi.h"

static struct rk_screen *rk_screen;

int rk_fb_get_extern_screen(struct rk_screen *screen)
{
	if (unlikely(!rk_screen) || unlikely(!screen))
		return -1;

	memcpy(screen, rk_screen, sizeof(struct rk_screen));
	screen->dsp_lut = NULL;
	screen->cabc_lut = NULL;
	screen->type = SCREEN_NULL;

	return 0;
}

int  rk_fb_get_prmry_screen(struct rk_screen *screen)
{
	if (unlikely(!rk_screen) || unlikely(!screen))
		return -1;

	memcpy(screen, rk_screen, sizeof(struct rk_screen));
	return 0;
}

int rk_fb_set_prmry_screen(struct rk_screen *screen)
{
	if (unlikely(!rk_screen) || unlikely(!screen))
		return -1;

	rk_screen->lcdc_id = screen->lcdc_id;
	rk_screen->screen_id = screen->screen_id;
	rk_screen->x_mirror = screen->x_mirror;
	rk_screen->y_mirror = screen->y_mirror;
	rk_screen->overscan.left = screen->overscan.left;
	rk_screen->overscan.top = screen->overscan.left;
	rk_screen->overscan.right = screen->overscan.left;
	rk_screen->overscan.bottom = screen->overscan.left;
	return 0;
}

size_t get_fb_size(u8 reserved_fb)
{
	size_t size = 0;
	u32 xres = 0;
	u32 yres = 0;

	if (unlikely(!rk_screen))
		return 0;

	xres = rk_screen->mode.xres;
	yres = rk_screen->mode.yres;

	/* align as 64 bytes(16*4) in an odd number of times */
	xres = ALIGN_64BYTE_ODD_TIMES(xres, ALIGN_PIXEL_64BYTE_RGB8888);
        if (reserved_fb == 1) {
                size = (xres * yres << 2) << 1;/*two buffer*/
        } else {
#if defined(CONFIG_THREE_FB_BUFFER)
		size = (xres * yres << 2) * 3;	/* three buffer */
#else
		size = (xres * yres << 2) << 1; /* two buffer */
#endif
	}
	return ALIGN(size, SZ_1M);
}

static int rk_screen_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	int ret;

	if (!np) {
		dev_err(&pdev->dev, "Missing device tree node.\n");
		return -EINVAL;
	}
	rk_screen = devm_kzalloc(&pdev->dev,
			sizeof(struct rk_screen), GFP_KERNEL);
	if (!rk_screen) {
		dev_err(&pdev->dev, "kmalloc for rk screen fail!");
		return  -ENOMEM;
	}
	ret = rk_fb_prase_timing_dt(np, rk_screen);
	dev_info(&pdev->dev, "rockchip screen probe %s\n",
				ret ? "failed" : "success");
	return ret;
}

static const struct of_device_id rk_screen_dt_ids[] = {
	{ .compatible = "rockchip,screen", },
	{}
};

static struct platform_driver rk_screen_driver = {
	.probe		= rk_screen_probe,
	.driver		= {
		.name	= "rk-screen",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(rk_screen_dt_ids),
	},
};

static int __init rk_screen_init(void)
{
	return platform_driver_register(&rk_screen_driver);
}

static void __exit rk_screen_exit(void)
{
	platform_driver_unregister(&rk_screen_driver);
}

fs_initcall(rk_screen_init);
module_exit(rk_screen_exit);

rk_fb.c

./kernel/drivers/video/rockchip/rk_fb.c


static const struct of_device_id rkfb_dt_ids[] = {
	{.compatible = "rockchip,rk-fb",},
	{}
};

static struct platform_driver rk_fb_driver = {
	.probe = rk_fb_probe,
	.remove = rk_fb_remove,
	.driver = {
		   .name = "rk-fb",
		   .owner = THIS_MODULE,
		   .of_match_table = of_match_ptr(rkfb_dt_ids),
		   },
	.shutdown = rk_fb_shutdown,
};

static int __init rk_fb_init(void)
{
	return platform_driver_register(&rk_fb_driver);
}

static void __exit rk_fb_exit(void)
{
	platform_driver_unregister(&rk_fb_driver);
}

fs_initcall(rk_fb_init);
module_exit(rk_fb_exit);

rk32_lvds.c

./kernel/drivers/video/rockchip/transmitter/rk32_lvds.c


#if defined(CONFIG_OF)
static const struct of_device_id rk32_lvds_dt_ids[] = {
	{.compatible = "rockchip,rk32-lvds",},
	{}
};

MODULE_DEVICE_TABLE(of, rk32_lvds_dt_ids);
#endif

static struct platform_driver rk32_lvds_driver = {
	.probe = rk32_lvds_probe,
	.driver = {
		   .name = "rk32-lvds",
		   .owner = THIS_MODULE,
#if defined(CONFIG_OF)
		   .of_match_table = of_match_ptr(rk32_lvds_dt_ids),
#endif
	},
	.shutdown = rk32_lvds_shutdown,
};

static int __init rk32_lvds_module_init(void)
{
	return platform_driver_register(&rk32_lvds_driver);
}

static void __exit rk32_lvds_module_exit(void)
{

}

fs_initcall(rk32_lvds_module_init);
module_exit(rk32_lvds_module_exit);

rk32_dp.c

./kernel/drivers/video/rockchip/transmitter/rk32_dp.c


#if defined(CONFIG_OF)
static const struct of_device_id rk32_edp_dt_ids[] = {
	{.compatible = "rockchip,rk32-edp", .data = (void *)SOC_COMMON},
	{.compatible = "rockchip,rk3399-edp-fb", .data = (void *)SOC_RK3399},
	{}
};

MODULE_DEVICE_TABLE(of, rk32_edp_dt_ids);
#endif

static struct platform_driver rk32_edp_driver = {
	.probe = rk32_edp_probe,
	.remove = rockchip_edp_remove,
	.driver = {
		   .name = "rk32-edp",
		   .owner = THIS_MODULE,
#if defined(CONFIG_OF)
		   .of_match_table = of_match_ptr(rk32_edp_dt_ids),
#endif
	},
};

static int __init rk32_edp_module_init(void)
{
	return platform_driver_register(&rk32_edp_driver);
}

static void __exit rk32_edp_module_exit(void)
{
}

fs_initcall(rk32_edp_module_init);
module_exit(rk32_edp_module_exit);

rk32_mipi_dsi.c

./kernel/drivers/video/rockchip/transmitter/rk32_mipi_dsi.c


static struct platform_driver rk32_mipi_dsi_driver = {
	.probe = rk32_mipi_dsi_probe,
	.remove = rockchip_mipi_remove,
	.driver = {
		.name = "rk32-mipi",
		.owner = THIS_MODULE,
#ifdef CONFIG_OF
		.of_match_table	= of_rk_mipi_dsi_match,
#endif
	},
};

static int __init rk32_mipi_dsi_init(void)
{
	return platform_driver_register(&rk32_mipi_dsi_driver);
}
fs_initcall(rk32_mipi_dsi_init);

static void __exit rk32_mipi_dsi_exit(void)
{
	platform_driver_unregister(&rk32_mipi_dsi_driver);
}
module_exit(rk32_mipi_dsi_exit);
#endif

APP层面

Android 的标准实现是使用 API Presentation 来实现异显的功能。 Presentation 是扩展自 dialog.

Presentation 是 Android 针对双屏异显所开发的一个类。它可以做到一个 APK 里面,通过给Presentation 单独进行 view 的布局,来实现同一个 APK 在主屏和副屏上面显示不同的 view,来达到异显的效果。 它的工作原理是通过调用 DisplayManagerService 的 getDisplays 方法来获取第二个显示设备。将第二个显示设备作为参数传给 Presentation,然后在 Presentation 里面实现自己的 UI内容, 最终调用 Presentation 的 show 方法来将 UI 内容显示在第二个显示设备上面