diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 15000e21840..02677ce19d5 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -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 diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 082b8967982..99abf0122a2 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -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/ diff --git a/drivers/video/mt8183_scanout.c b/drivers/video/mt8183_scanout.c new file mode 100644 index 00000000000..dc342b93e2c --- /dev/null +++ b/drivers/video/mt8183_scanout.c @@ -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 +#include +#include +#include +#include +#include +#include + +/* + * 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, +};