硬件层面
SOC 内部需要集成两个 LCDC 控制器
驱动层面
-
保证两个屏幕单独作为主屏的时候都可以正常显示,这样子保证了两个屏幕的屏参都是正确的,而且两个屏幕的物理连接都正常
-
双屏显示控制部分的实现:默认情况下 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 内容显示在第二个显示设备上面