video: add MT8183 scanout driver for firmware-initialized displays
Add a display driver for the MT8183 kukui family of ChromeOS devices (e.g. the Lenovo IdeaPad Duet, google,krane), where the display pipeline is already initialized and running when the payload is handed off by depthcharge. depthcharge's display_cleanup() stops the overlay engines and turns the backlight off before the jump, so the driver only revives the pipeline: it re-enables the OVL0 engines (OVL_EN @ +0x000c and OVL0_2L_EN @ +0x100c, offsets from depthcharge's device-era mtk_ddp.c), drives the backlight GPIOs high (DISP_PWM, pin 43, and EN_LCD_BL, pin 176, through the GPIO controller dout block at 0x10005000+0x100), and renders the console into the live scanout surface. The geometry comes from the coreboot table framebuffer record, which sits at the fixed kukui address 0xffed9000; when its physical_address is 0 (what upstream coreboot 4.14 publishes on kukui), the live scanout address is taken from the OVL layer 0 address register (+0x0f40), which the firmware leaves in place across the handoff. The driver binds to the existing ovl0 device tree node (mediatek,mt8183-disp-ovl) and requires no display initialization.
This commit is contained in:
@@ -1022,6 +1022,19 @@ config VIDEO_MCDE_SIMPLE
|
||||
before u-boot starts, and u-boot will simply render to the pre-
|
||||
allocated frame buffer surface.
|
||||
|
||||
config VIDEO_MT8183_SCANOUT
|
||||
bool "Enable MT8183 scanout driver for firmware-initialized displays"
|
||||
depends on ARCH_MEDIATEK
|
||||
help
|
||||
Enables a display driver for the MediaTek MT8183 on boards where
|
||||
the display pipeline has already been initialized by the boot
|
||||
firmware (e.g. ChromeOS coreboot + depthcharge on the kukui
|
||||
family). The driver revives the overlay engines and backlight that
|
||||
the firmware disables before handoff, discovers the live scanout
|
||||
surface from the coreboot table (with a fallback to the overlay
|
||||
layer address register), and lets the standard vidconsole render
|
||||
into it. No display initialization is performed.
|
||||
|
||||
config OSD
|
||||
bool "Enable OSD support"
|
||||
depends on DM
|
||||
|
||||
@@ -80,6 +80,7 @@ obj-$(CONFIG_VIDEO_LCD_SAMSUNG_S6E63M0) += samsung-s6e63m0.o
|
||||
obj-$(CONFIG_VIDEO_MCDE_SIMPLE) += mcde_simple.o
|
||||
obj-${CONFIG_VIDEO_MESON} += meson/
|
||||
obj-${CONFIG_VIDEO_MIPI_DSI} += mipi_dsi.o
|
||||
obj-$(CONFIG_VIDEO_MT8183_SCANOUT) += mt8183_scanout.o
|
||||
obj-$(CONFIG_VIDEO_MVEBU) += mvebu_lcd.o
|
||||
obj-$(CONFIG_VIDEO_MXS) += mxsfb.o videomodes.o
|
||||
obj-$(CONFIG_VIDEO_NX) += nexell_display.o videomodes.o nexell/
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* MT8183 display scanout driver for firmware-initialized pipelines.
|
||||
*
|
||||
* On the MT8183 kukui family of ChromeOS devices (e.g. the Lenovo IdeaPad
|
||||
* Duet, "google,krane"), the display pipeline (MMSYS -> OVL0 -> OVL0_2L ->
|
||||
* RDMA -> COLOR -> DSI -> panel) is fully initialized and running by the
|
||||
* time control is passed to the next boot stage: coreboot (through
|
||||
* libpayload/depthcharge) sets up the panel and programs the overlay scanout
|
||||
* address. The payload, however, is handed over with the pipeline STOPPED:
|
||||
* depthcharge's display_cleanup() disables the overlay engines and turns the
|
||||
* backlight off right before jumping to the payload.
|
||||
*
|
||||
* This driver does not initialize any display hardware. It only revives the
|
||||
* pipeline (re-enabling the overlay engines and the backlight), discovers
|
||||
* the geometry and the live scanout address from the coreboot table
|
||||
* framebuffer record (falling back to the OVL L0 layer address register,
|
||||
* which still holds the address depthcharge used), and hands the surface to
|
||||
* the video uclass so the standard vidconsole can render into it.
|
||||
*/
|
||||
|
||||
#include <dm.h>
|
||||
#include <video.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
/*
|
||||
* OVL0 register offsets. Source: Linux drivers/gpu/drm/mediatek/
|
||||
* mtk_disp_ovl.c (DISP_REG_OVL_EN) and device-era depthcharge
|
||||
* src/drivers/video/mtk_ddp.c (ChromeOS R93, the generation shipped on
|
||||
* kukui); both agree on 0x000c for OVL_EN. The 2L sub-engine enable
|
||||
* (DISP_REG_OVL0_2L_EN) lives in the same register block on MT8183.
|
||||
* The layer-0 source address register (DISP_REG_OVL_L0_ADDR) is not reset
|
||||
* by depthcharge's stop() and holds the live scanout address.
|
||||
*/
|
||||
#define DISP_REG_OVL_L0_ADDR 0x0f40
|
||||
#define DISP_REG_OVL_EN 0x000c
|
||||
#define DISP_REG_OVL0_2L_EN 0x100c
|
||||
|
||||
/*
|
||||
* Backlight GPIOs. The MT8183 GPIO controller is at 0x10005000; the dout
|
||||
* block starts at +0x100 with 16 bytes per 32-pin group and set@+4 (layout
|
||||
* of GpioRegs/GpioValRegs in device-era depthcharge src/drivers/gpio/
|
||||
* mt8183.h). On kukui the backlight is driven by two dedicated GPIOs:
|
||||
* DISP_PWM (pin 43) and EN_LCD_BL (PERIPHERAL_EN13, pin 176); depthcharge's
|
||||
* kukui_backlight_update() drives both high to turn the backlight on.
|
||||
*/
|
||||
#define MTK_GPIO_BASE 0x10005000
|
||||
#define MTK_GPIO_DOUT_SET(pin) (MTK_GPIO_BASE + 0x100 + ((pin) / 32) * 16 + 4)
|
||||
#define MTK_GPIO_DOUT_BIT(pin) BIT((pin) % 32)
|
||||
#define PAD_DISP_PWM 43
|
||||
#define PAD_EN_LCD_BL 176
|
||||
|
||||
/*
|
||||
* The coreboot table sits at a fixed address on kukui: 0xffed9000, size
|
||||
* 0x380 (coreboot memlayout; confirmed on the device through
|
||||
* /sys/firmware/fdt and the coreboot sysfs tags). It is deliberately
|
||||
* hardcoded here rather than read from the handoff DTB: on this platform
|
||||
* the FDT pointer passed at entry cannot be relied upon, and U-Boot boots
|
||||
* with its own embedded control DTB which has no /firmware/coreboot node.
|
||||
*/
|
||||
#define COREBOOT_TABLE_ADDR 0xffed9000
|
||||
|
||||
#define LB_TAG_FRAMEBUFFER 0x12
|
||||
|
||||
/*
|
||||
* Layout from coreboot src/commonlib/include/commonlib/coreboot_tables.h:
|
||||
* struct lb_header: sig[4] "LBIO", header_bytes, header_checksum,
|
||||
* table_bytes, table_checksum, table_entries (LE u32)
|
||||
* struct lb_record: tag, size
|
||||
* struct lb_framebuffer (record payload):
|
||||
* physical_address @8 (4-byte-aligned LE u64), x_resolution @16,
|
||||
* y_resolution @20, bytes_per_line @24, bits_per_pixel @28,
|
||||
* red_pos/size @29/30, green @31/32, blue @33/34,
|
||||
* reserved @35/36, orientation @37, flags @38, pad @39; size 40.
|
||||
*
|
||||
* physical_address == 0 is legitimate: upstream coreboot 4.14 (the
|
||||
* generation shipped on kukui) publishes the framebuffer record with
|
||||
* fb_addr=0. The live scanout address then comes from OVL_L0_ADDR.
|
||||
*/
|
||||
struct lb_framebuffer {
|
||||
u32 tag;
|
||||
u32 size;
|
||||
u64 physical_address;
|
||||
u32 x_resolution;
|
||||
u32 y_resolution;
|
||||
u32 bytes_per_line;
|
||||
u8 bits_per_pixel;
|
||||
u8 red_pos;
|
||||
u8 red_size;
|
||||
u8 green_pos;
|
||||
u8 green_size;
|
||||
u8 blue_pos;
|
||||
u8 blue_size;
|
||||
u8 reserved_pos;
|
||||
u8 reserved_size;
|
||||
};
|
||||
|
||||
static int find_framebuffer(u64 table, struct lb_framebuffer *fb)
|
||||
{
|
||||
void *base = (void *)(uintptr_t)table;
|
||||
u32 header_bytes, entries, i;
|
||||
void *rec;
|
||||
|
||||
if (get_unaligned_le32(base) != 0x4f49424c) /* "LBIO" */
|
||||
return -ENOENT;
|
||||
|
||||
header_bytes = get_unaligned_le32(base + 4);
|
||||
entries = get_unaligned_le32(base + 20);
|
||||
if (header_bytes < 24 || header_bytes > 4096 ||
|
||||
entries == 0 || entries > 4096)
|
||||
return -EINVAL;
|
||||
|
||||
rec = base + header_bytes;
|
||||
for (i = 0; i < entries; i++) {
|
||||
u32 tag = get_unaligned_le32(rec);
|
||||
u32 rsize = get_unaligned_le32(rec + 4);
|
||||
|
||||
if (rsize < 8)
|
||||
return -EINVAL;
|
||||
|
||||
if (tag == LB_TAG_FRAMEBUFFER) {
|
||||
if (rsize < sizeof(*fb))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(fb, rec, sizeof(*fb));
|
||||
fb->physical_address =
|
||||
get_unaligned_le64(rec + 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rec += rsize;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int mt8183_scanout_probe(struct udevice *dev)
|
||||
{
|
||||
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
|
||||
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
|
||||
struct lb_framebuffer fb;
|
||||
fdt_addr_t ovl;
|
||||
u64 addr;
|
||||
int ret;
|
||||
|
||||
ovl = dev_read_addr(dev);
|
||||
if (ovl == FDT_ADDR_T_NONE)
|
||||
return log_msg_ret("ovl", -EINVAL);
|
||||
|
||||
/*
|
||||
* Revive the pipeline: undo depthcharge's display_cleanup() by
|
||||
* re-enabling the overlay engines and driving the backlight GPIOs
|
||||
* high. No panel or DSI re-initialization is needed: the panel is
|
||||
* powered and the DSI link stays up through the handoff.
|
||||
*/
|
||||
writel(1, ovl + DISP_REG_OVL_EN);
|
||||
writel(1, ovl + DISP_REG_OVL0_2L_EN);
|
||||
writel(MTK_GPIO_DOUT_BIT(PAD_DISP_PWM),
|
||||
(void __iomem *)MTK_GPIO_DOUT_SET(PAD_DISP_PWM));
|
||||
writel(MTK_GPIO_DOUT_BIT(PAD_EN_LCD_BL),
|
||||
(void __iomem *)MTK_GPIO_DOUT_SET(PAD_EN_LCD_BL));
|
||||
|
||||
/*
|
||||
* The coreboot table sits above the DRAM window described by the
|
||||
* control DTB; map it before parsing.
|
||||
*/
|
||||
mmu_map_region(COREBOOT_TABLE_ADDR, SZ_4K, false);
|
||||
|
||||
ret = find_framebuffer(COREBOOT_TABLE_ADDR, &fb);
|
||||
if (ret)
|
||||
return log_msg_ret("lbio", ret);
|
||||
|
||||
if (fb.bits_per_pixel != 32 || fb.red_pos != 16 || fb.red_size != 8 ||
|
||||
fb.green_pos != 8 || fb.green_size != 8 ||
|
||||
fb.blue_pos != 0 || fb.blue_size != 8)
|
||||
return log_msg_ret("fmt", -ENOTSUPP);
|
||||
|
||||
/*
|
||||
* Use the address from the coreboot table when it is a plausible
|
||||
* DRAM address (>= 1 GiB), otherwise fall back to the address the
|
||||
* firmware actually programmed into the overlay, which it left in
|
||||
* place across the handoff.
|
||||
*/
|
||||
addr = fb.physical_address;
|
||||
if (addr < SZ_1G)
|
||||
addr = readl(ovl + DISP_REG_OVL_L0_ADDR);
|
||||
|
||||
if (addr < SZ_1G)
|
||||
return log_msg_ret("scanout", -ENODEV);
|
||||
|
||||
plat->base = addr;
|
||||
plat->size = fb.bytes_per_line * fb.y_resolution;
|
||||
|
||||
/* The scanout surface is above the DTB DRAM window: map it. */
|
||||
mmu_map_region(plat->base, ALIGN(plat->size, SZ_4K), false);
|
||||
video_set_flush_dcache(dev, true);
|
||||
|
||||
uc_priv->bpix = VIDEO_BPP32;
|
||||
uc_priv->xsize = fb.x_resolution;
|
||||
uc_priv->ysize = fb.y_resolution;
|
||||
uc_priv->line_length = fb.bytes_per_line;
|
||||
|
||||
printf("Video: MT8183 scanout %dx%d@32bpp at %llx\n",
|
||||
uc_priv->xsize, uc_priv->ysize, (unsigned long long)plat->base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id mt8183_scanout_ids[] = {
|
||||
{ .compatible = "mediatek,mt8183-disp-ovl" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(mt8183_scanout) = {
|
||||
.name = "mt8183_scanout",
|
||||
.id = UCLASS_VIDEO,
|
||||
.of_match = mt8183_scanout_ids,
|
||||
.probe = mt8183_scanout_probe,
|
||||
};
|
||||
Reference in New Issue
Block a user