diff --git a/MAINTAINERS b/MAINTAINERS index 591a8997e71..985efc3bebb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4,7 +4,11 @@ F: drivers/phy/phy-mtk-mipi-tx.c F: drivers/power/domain/mt* F: drivers/power/regulator/mt*.c F: drivers/usb/mtu3/ +F: drivers/video/mt8183_disp.c +F: drivers/video/mt8183_disp.h +F: drivers/video/mt8183_display.c F: drivers/video/mtk_dsi.c +F: drivers/video/panel_boe_tv101wum.c F: include/power/mt*.h diff --git a/configs/mt8183_kukui_krane_defconfig b/configs/mt8183_kukui_krane_defconfig index f14aa5d5cf3..0a81c811a0a 100644 --- a/configs/mt8183_kukui_krane_defconfig +++ b/configs/mt8183_kukui_krane_defconfig @@ -62,7 +62,11 @@ CONFIG_ENV_SOURCE_FILE="krane" CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y CONFIG_CLK=y CONFIG_VIDEO=y -CONFIG_VIDEO_MT8183_SCANOUT=y +CONFIG_MT8183_GPIO=y +CONFIG_POWER=y +CONFIG_DM_REGULATOR=y +CONFIG_DM_REGULATOR_FIXED=y +CONFIG_VIDEO_MT8183_DISPLAY=y # CONFIG_INPUT is not set # CONFIG_MMC_QUIRKS is not set CONFIG_MMC_MTK=y @@ -75,8 +79,7 @@ CONFIG_USB_XHCI_HCD=y CONFIG_USB_XHCI_MTK=y CONFIG_USB_KEYBOARD=y CONFIG_USB_STORAGE=y -# CONFIG_POWER is not set -CONFIG_BAUDRATE=921600 +CONFIG_BAUDRATE=115200 CONFIG_DM_SERIAL=y CONFIG_DEBUG_UART_ANNOUNCE=y CONFIG_MTK_SERIAL=y diff --git a/drivers/clk/mediatek/clk-mt8183.c b/drivers/clk/mediatek/clk-mt8183.c index 13a57161706..12a74da66ab 100644 --- a/drivers/clk/mediatek/clk-mt8183.c +++ b/drivers/clk/mediatek/clk-mt8183.c @@ -787,9 +787,39 @@ static const struct mtk_gate infra_clks[] = { GATE_INFRA3(CLK_INFRA_FBIST2FPC, CLK_TOP_MUX_MSDC50_0, 24), }; +/* + * The gate arrays follow the register bit order of each clock domain, + * while the device tree uses the clock IDs of the legacy clock header; + * the two do not agree everywhere. Map each DT clock ID to its array + * position; -1 marks IDs that have no gate. + */ +static const int mt8183_id_infra_offs_map[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 52, 53, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, -1, 67, -1, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, -1, -1, 84, 85, 86, 87, + 54, 51, 88, 89, 90, 91, 92, 93, 94, 95, + 96, +}; + +static const int mt8183_id_mm_offs_map[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 18, 42, 43, 44, 45, +}; + static const struct mtk_clk_tree mt8183_infracfg_tree = { .ext_clk_rates = ext_clock_rates, .num_ext_clks = ARRAY_SIZE(ext_clock_rates), + .id_offs_map = mt8183_id_infra_offs_map, + .id_offs_map_size = ARRAY_SIZE(mt8183_id_infra_offs_map), .gates = infra_clks, .num_gates = ARRAY_SIZE(infra_clks), }; @@ -878,6 +908,8 @@ static const struct mtk_gate mm_clks[] = { }; static const struct mtk_clk_tree mt8183_mmsys_tree = { + .id_offs_map = mt8183_id_mm_offs_map, + .id_offs_map_size = ARRAY_SIZE(mt8183_id_mm_offs_map), .gates = mm_clks, .num_gates = ARRAY_SIZE(mm_clks), }; diff --git a/drivers/serial/serial_mtk.c b/drivers/serial/serial_mtk.c index 01cc415efdd..4aaae5f621f 100644 --- a/drivers/serial/serial_mtk.c +++ b/drivers/serial/serial_mtk.c @@ -171,6 +171,17 @@ set_baud: writel((quot >> 8) & 0xff, &priv->regs->dlm); writel(UART_LCR_WLS_8, &priv->regs->lcr); + /* + * In low speed mode the sample count is fixed to 16 and the + * sample point and fraction registers are meaningless. Leave + * them at the values the boot firmware programmed (the same + * thing the coreboot 8250 driver does) instead of writing a + * zero sample count, which breaks the bit rate generator and + * garbles the output on boards the firmware already set up. + */ + if (samplecount <= 1) + return; + /* set highspeed mode sample count & point */ writel(samplecount - 1, &priv->regs->sample_count); writel((samplecount >> 1) - 1, &priv->regs->sample_point); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 4f0f41d3252..d50d4b5e74c 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1022,9 +1022,16 @@ config VIDEO_MCDE_SIMPLE before u-boot starts, and u-boot will simply render to the pre- allocated frame buffer surface. +choice + prompt "MT8183 display driver" + depends on ARCH_MEDIATEK + help + Selects which driver drives the display pipeline on MT8183 + boards: revive the pipeline initialized by the boot firmware, or + bring it up from scratch. + 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 @@ -1035,6 +1042,27 @@ config VIDEO_MT8183_SCANOUT layer address register), and lets the standard vidconsole render into it. No display initialization is performed. +config VIDEO_MT8183_DISPLAY + bool "Enable MT8183 display pipeline bring-up driver" + depends on VIDEO_MIPI_DSI && PANEL && PHY + select VIDEO_MTK_DSI + select PHY_MTK_MIPI_TX + select PANEL_BOE_TV101WUM + help + Enables a display driver for the MediaTek MT8183 that brings the + full display pipeline up from scratch: MMSYS display clock gates, + MIPI TX D-PHY, MIPI DSI host, panel initialization (BOE + TV101WUM-NL6 and friends on the kukui family), overlay scanout of + an U-Boot-allocated framebuffer and backlight. The framebuffer is + placed outside the DRAM window described by the control device + tree and reserved in the FDT and LMB. + + If the pipeline fails to come up, the driver falls back to + reviving the pipeline left running by the boot firmware (see + VIDEO_MT8183_SCANOUT) and reports which path was taken. + +endchoice + config OSD bool "Enable OSD support" depends on DM @@ -1450,3 +1478,11 @@ config VIDEO_MTK_DSI video mode to stream out pixels. It relies on the MMSYS display gates being available through the clock framework and on the MT8183 MIPI TX D-PHY (PHY_MTK_MIPI_TX). + +config PANEL_BOE_TV101WUM + bool "Enable BOE TV101WUM-NL6 DSI panel driver" + depends on PANEL && VIDEO_MIPI_DSI + help + Say Y here if you want to enable support for the BOE + TV101WUM-NL6 MIPI DSI panel (1200x1920, 4 data lanes), used in + the Lenovo IdeaPad Duet (google,krane) and other devices. diff --git a/drivers/video/Makefile b/drivers/video/Makefile index a609e8fc06f..a9e37fdcabb 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -80,7 +80,8 @@ 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_MT8183_SCANOUT) += mt8183_scanout.o mt8183_disp.o +obj-$(CONFIG_VIDEO_MT8183_DISPLAY) += mt8183_display.o mt8183_disp.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/ @@ -96,3 +97,4 @@ obj-y += bridge/ obj-y += sunxi/ obj-y += tegra/ obj-$(CONFIG_VIDEO_MTK_DSI) += mtk_dsi.o +obj-$(CONFIG_PANEL_BOE_TV101WUM) += panel_boe_tv101wum.o diff --git a/drivers/video/mt8183_disp.c b/drivers/video/mt8183_disp.c new file mode 100644 index 00000000000..db615e28958 --- /dev/null +++ b/drivers/video/mt8183_disp.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek MT8183 display helpers shared by the display drivers. + * + * Copyright (C) 2026 Vincent Haudiquet + * + * The firmware-handoff model documented here applies to 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, but depthcharge's + * display_cleanup() disables the overlay engines and turns the backlight + * off right before jumping to the payload. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mt8183_disp.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 + +#define LB_TAG_FRAMEBUFFER 0x12 + +void mt8183_disp_enable_backlight(void) +{ + 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)); +} + +int mt8183_disp_find_framebuffer(u64 table, + struct mt8183_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; +} + +int mt8183_disp_setup_handoff(fdt_addr_t ovl, struct video_uc_plat *plat, + struct video_priv *uc_priv) +{ + struct mt8183_lb_framebuffer fb; + u64 addr; + int ret; + + /* + * 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); + mt8183_disp_enable_backlight(); + + /* + * 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 = mt8183_disp_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", -EOPNOTSUPP); + + /* + * 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 buffer was allocated by the boot firmware and is not + * tracked by U-Boot's memory management. Without a reservation it + * shows up as conventional RAM: once the kernel's page allocator + * goes live it reuses the region and the display corrupts. Record a + * memory reservation in the control FDT and feed it to LMB, so that + * EFI page allocations skip it, the EFI DT fixup marks it reserved + * in the memory map, and the kernel honors the /memreserve of the + * DTB it receives. + */ + ret = fdt_add_mem_rsv((void *)gd->fdt_blob, plat->base, plat->size); + if (ret) + log_warning("FDT reservation for scanout buffer failed: %d\n", + ret); + else + boot_fdt_add_mem_rsv_regions(gd->fdt_blob); + + /* The scanout surface is above the DTB DRAM window: map it. */ + mmu_map_region(plat->base, ALIGN(plat->size, SZ_4K), false); + + 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; + + /* + * The BOE TV101WUM-NL6 panel is mounted rotated 270 degrees in + * the krane chassis (see the panel node's "rotation" property in + * the upstream DT). The keyboard-covered landscape orientation + * therefore needs the console rotated; the uclass then binds the + * rotated vidconsole3 text driver. rot=1 (90 degrees) was tried + * first and read upside down on the device. + */ + uc_priv->rot = 3; + + return 0; +} diff --git a/drivers/video/mt8183_disp.h b/drivers/video/mt8183_disp.h new file mode 100644 index 00000000000..2744c9708b5 --- /dev/null +++ b/drivers/video/mt8183_disp.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MediaTek MT8183 display helpers shared by the display drivers. + * + * Copyright (C) 2026 Vincent Haudiquet + */ + +#ifndef _MT8183_DISP_H +#define _MT8183_DISP_H + +#include + +/* + * 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). + */ +#define COREBOOT_TABLE_ADDR 0xffed9000 + +struct mt8183_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; +}; + +/** + * mt8183_disp_enable_backlight() - drive the two kukui backlight GPIOs on + * + * Board glue for the krane backlight (DISP_PWM and EN_LCD_BL are plain + * GPIOs on this board; the upstream backlight node describes a PWM setup + * whose pin muxing the boot firmware already provides). + */ +void mt8183_disp_enable_backlight(void); + +/** + * mt8183_disp_find_framebuffer() - parse the coreboot table LBIO record + * + * Layout from coreboot src/commonlib/include/commonlib/coreboot_tables.h. + * 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. + */ +int mt8183_disp_find_framebuffer(u64 table, + struct mt8183_lb_framebuffer *fb); + +/** + * mt8183_disp_setup_handoff() - revive the firmware pipeline and take over + * its scanout surface + * + * Re-enables the overlay engines and the backlight (undoing depthcharge's + * display_cleanup()), discovers the geometry and the live scanout address + * from the coreboot table framebuffer record (falling back to the OVL + * layer-0 address register, which still holds the address the firmware + * used), maps the surface and reserves it in the control FDT and LMB, and + * fills @plat and @uc_priv with the resulting geometry. + * + * @ovl: OVL0 base address (already mapped) + * Return: 0 if OK, -ve on error + */ +int mt8183_disp_setup_handoff(fdt_addr_t ovl, struct video_uc_plat *plat, + struct video_priv *uc_priv); + +#endif diff --git a/drivers/video/mt8183_display.c b/drivers/video/mt8183_display.c new file mode 100644 index 00000000000..69f0191bf9f --- /dev/null +++ b/drivers/video/mt8183_display.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek MT8183 display driver: full pipeline bring-up. + * + * Copyright (C) 2026 Vincent Haudiquet + * + * On the MT8183 kukui family of ChromeOS devices (e.g. the Lenovo IdeaPad + * Duet, "google,krane") the display pipeline is + * + * MMSYS -> OVL0 -> OVL0_2L -> RDMA0 -> COLOR0 -> CCORR0 -> AAL0 -> + * GAMMA0 -> DITHER0 -> DSI0 -> panel + * + * This driver brings that pipeline up from scratch, ported from the + * ChromeOS coreboot port for this platform (src/soc/mediatek/mt8183/ + * ddp.c, src/soc/mediatek/mt8183/include/soc/ddp.h and the mainboard + * display bring-up in src/mainboard/google/kukui/mainboard.c, all in + * coreboot 4.14, the generation shipped on these devices): + * + * - enable the MMSYS free-run clock gates for the display engines and + * take the SMI local arbiter out of the M4U domain (the OVL fetches + * the framebuffer directly from DRAM in U-Boot), + * - power and initialize the panel over DSI (panel uclass child of the + * DSI controller node) and start the DSI video stream, + * - route and enable the overlay chain, configure the overlay layer + * for the framebuffer surface, + * - turn the backlight on. + * + * If any stage fails (for example when the boot firmware has already + * shut the pipeline down in a way this sequence cannot recover), the + * driver falls back to reviving the pipeline left running by the boot + * firmware instead, and reports which path was taken. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mt8183_disp.h" + +/* + * Base addresses of the display pipeline components. Source: coreboot + * 4.14 src/soc/mediatek/mt8183/include/soc/addressmap.h (IO_PHYS + + * offsets, matching the upstream device tree reg properties). MT8183 has + * a single MMSYS instance and one display path, so these are fixed for + * the SoC. Note the naming difference with coreboot, whose + * DISP_OVL1_BASE (0x14009000) is the OVL0_2L engine of the upstream + * device tree (node ovl_2l0@14009000). + */ +#define MMSYS_BASE 0x14000000 +#define DISP_OVL0_BASE 0x14008000 +#define DISP_OVL0_2L_BASE 0x14009000 +#define DISP_RDMA0_BASE 0x1400b000 +#define DISP_COLOR0_BASE 0x1400e000 +#define DISP_CCORR0_BASE 0x1400f000 +#define DISP_AAL0_BASE 0x14010000 +#define DISP_GAMMA0_BASE 0x14011000 +#define DISP_DITHER0_BASE 0x14012000 +#define DISP_MUTEX_BASE 0x14016000 +#define SMI_LARB0 0x14017000 + +/* + * MMSYS free-run clock gates (1 = gated). Offsets and bit assignments + * from coreboot 4.14 src/soc/mediatek/mt8183/include/soc/ddp.h + * (mmsys_cg_con0 at 0x100, mmsys_cg_con1 at 0x110); the same bits are + * modeled as CLK_MM_* gate clocks by the clock framework. + */ +#define MMSYS_CG_CON0 0x100 +#define MMSYS_CG_CON1 0x110 +#define CG_CON0_DISP_ALL (BIT(0) | BIT(1) | BIT(3) | BIT(4) | \ + BIT(20) | BIT(21) | BIT(23) | BIT(26) | \ + BIT(27) | BIT(28) | BIT(29) | BIT(30)) +#define CG_CON1_DISP_DSI0 (BIT(0) | BIT(1)) + +/* MMSYS routing registers, from the coreboot ddp.h register map. */ +#define DISP_OVL0_MOUT_EN 0xf00 +#define DISP_OVL0_2L_MOUT_EN 0xf04 +#define DISP_DITHER0_MOUT_EN 0xf0c +#define DISP_PATH0_SEL_IN 0xf24 +#define DSI0_SEL_IN 0xf2c +#define DISP_RDMA0_SOUT_SEL_IN 0xf50 + +#define OVL0_MOUT_EN_OVL0_2L BIT(4) +#define OVL0_2L_MOUT_EN_DISP_PATH0 BIT(0) +#define DITHER0_MOUT_EN_DISP_DSI0 BIT(0) +#define DISP_PATH0_SEL_IN_OVL0_2L 1 +#define DSI0_SEL_IN_DITHER0_MOUT 0 +#define RDMA0_SOUT_SEL_IN_COLOR 1 + +/* DISP_MUTEX */ +#define MUTEX_MOD_DISP_RDMA0 BIT(0) +#define MUTEX_MOD_DISP_OVL0 BIT(9) +#define MUTEX_MOD_DISP_OVL0_2L BIT(10) +#define MUTEX_MOD_DISP_COLOR0 BIT(13) +#define MUTEX_MOD_DISP_CCORR0 BIT(14) +#define MUTEX_MOD_DISP_AAL0 BIT(15) +#define MUTEX_MOD_DISP_GAMMA0 BIT(16) +#define MUTEX_MOD_DISP_DITHER0 BIT(17) +#define MUTEX_MOD_MAIN_PATH (MUTEX_MOD_DISP_OVL0 | \ + MUTEX_MOD_DISP_OVL0_2L | \ + MUTEX_MOD_DISP_RDMA0 | \ + MUTEX_MOD_DISP_COLOR0 | \ + MUTEX_MOD_DISP_CCORR0 | \ + MUTEX_MOD_DISP_AAL0 | \ + MUTEX_MOD_DISP_GAMMA0 | \ + MUTEX_MOD_DISP_DITHER0) +#define MUTEX_SOF_DSI0 1 + +/* + * OVL registers, from the coreboot ddp_common.h register map (struct + * disp_ovl_regs). MT8183 has 4-layer OVL engines; the main path uses + * layer 0 of OVL0 for the framebuffer. + */ +#define DISP_REG_OVL_EN 0x000c +#define DISP_REG_OVL_ROI_SIZE 0x0020 +#define DISP_REG_OVL_DATAPATH_CON 0x0024 +#define DISP_REG_OVL_ROI_BGCLR 0x0028 +#define DISP_REG_OVL_SRC_CON 0x002c +#define DISP_REG_OVL_L0_CON 0x0030 +#define DISP_REG_OVL_L0_SRC_SIZE 0x0038 +#define DISP_REG_OVL_L0_PITCH 0x0044 +#define DISP_REG_OVL_L0_RDMA_CTRL 0x0070 +#define DISP_REG_OVL_L0_RDMA_GMC 0x0078 +#define DISP_REG_OVL_L0_ADDR 0x0f40 + +#define OVL_INFMT_RGBA8888 2 +#define RDMA_MEM_GMC 0x40402020 + +/* DISP_RDMA0 registers, from the coreboot ddp_common.h register map. */ +#define DISP_REG_RDMA_GLOBAL_CON 0x0010 +#define DISP_REG_RDMA_SIZE_CON_0 0x0014 +#define DISP_REG_RDMA_SIZE_CON_1 0x0018 +#define DISP_REG_RDMA_FIFO_CON 0x0040 +#define RDMA_ENGINE_EN BIT(0) +#define RDMA_FIFO_UNDERFLOW_EN BIT(31) +#define RDMA_FIFO_PSEUDO_SIZE(bytes) (((bytes) / 16) << 16) +#define RDMA_OUTPUT_VALID_FIFO_THRESHOLD(bytes) ((bytes) / 16) + +/* DISP_COLOR0, from the coreboot ddp_common.h register map. */ +#define DISP_REG_COLOR_CFG_MAIN 0x0400 +#define DISP_REG_COLOR_START 0x0c00 +#define DISP_REG_COLOR_WIDTH 0x0c50 +#define DISP_REG_COLOR_HEIGHT 0x0c54 +#define COLOR_BYPASS_ALL BIT(7) +#define COLOR_SEQ_SEL BIT(13) + +/* + * The display "post processing" blocks (CCORR/AAL/GAMMA/DITHER) share + * this layout (struct disp_pq_regs of the coreboot ddp.h): enable at + * +0x0, config at +0x1c, size at +0x30. + */ +#define DISP_REG_PQ_EN 0x0000 +#define DISP_REG_PQ_CFG 0x001c +#define DISP_REG_PQ_SIZE 0x0030 +#define PQ_EN BIT(0) +#define PQ_RELAY_MODE BIT(0) + +#define SMI_LARB_NON_SEC_CON 0x380 + +/* + * The RDMA pseudo FIFO size: the mediatek,rdma-fifo-size property of the + * RDMA0 node in the upstream device tree (5 KiB, same value as the + * coreboot port passes to rdma_config()). + */ +#define RDMA_FIFO_SIZE (5 * 1024) + +/* + * The framebuffer sits inside the DRAM window the control device tree + * describes (2 GiB starting at DRAM_BASE), which both the LMB and EFI + * memory maps cover. The region is handed to the OS as reserved: see + * the STAGE_PIPELINE comment. + */ +#define DRAM_BASE 0x40000000 +#define FB_ADDR 0xbe000000 + +enum mt8183_disp_stage { + STAGE_CLOCKS, + STAGE_PANEL, + STAGE_DSI_INIT, + STAGE_DSI_ENABLE, + STAGE_PIPELINE, + STAGE_COUNT, +}; + +static const char *const stage_names[STAGE_COUNT] = { + "clocks", "panel", "dsi-init", "dsi-enable", "pipeline", +}; + +struct mt8183_display_priv { + struct udevice *dsi_host; + struct udevice *panel; + struct mipi_dsi_device device; + struct display_timing timing; +}; + +static void mt8183_disp_clock_on(void) +{ + void __iomem *mmsys = (void __iomem *)MMSYS_BASE; + + clrbits_le32(mmsys + MMSYS_CG_CON0, CG_CON0_DISP_ALL); + clrbits_le32(mmsys + MMSYS_CG_CON1, CG_CON1_DISP_DSI0); + + /* + * Turn off the M4U port of SMI LARB0: the OVL reads DRAM + * directly in U-Boot, without an IOMMU. + */ + writel(0, (void __iomem *)(SMI_LARB0 + SMI_LARB_NON_SEC_CON)); +} + +static void disp_config_main_path_connection(void) +{ + void __iomem *mmsys = (void __iomem *)MMSYS_BASE; + + writel(OVL0_MOUT_EN_OVL0_2L, mmsys + DISP_OVL0_MOUT_EN); + writel(OVL0_2L_MOUT_EN_DISP_PATH0, mmsys + DISP_OVL0_2L_MOUT_EN); + writel(DISP_PATH0_SEL_IN_OVL0_2L, mmsys + DISP_PATH0_SEL_IN); + writel(RDMA0_SOUT_SEL_IN_COLOR, mmsys + DISP_RDMA0_SOUT_SEL_IN); + writel(DITHER0_MOUT_EN_DISP_DSI0, mmsys + DISP_DITHER0_MOUT_EN); + writel(DSI0_SEL_IN_DITHER0_MOUT, mmsys + DSI0_SEL_IN); +} + +static void disp_config_main_path_mutex(void) +{ + void __iomem *mutex = (void __iomem *)DISP_MUTEX_BASE; + + /* mutex[0]: EN at +0x20, CTL at +0x2c, MOD at +0x30. */ + writel(MUTEX_MOD_MAIN_PATH, mutex + 0x30); + writel(MUTEX_SOF_DSI0 | (MUTEX_SOF_DSI0 << 6), mutex + 0x2c); + writel(BIT(0), mutex + 0x20); +} + +static void enable_pq(void __iomem *regs, u32 width, u32 height, + int enable_relay) +{ + writel(height << 16 | width, regs + DISP_REG_PQ_SIZE); + if (enable_relay) + writel(PQ_RELAY_MODE, regs + DISP_REG_PQ_CFG); + writel(PQ_EN, regs + DISP_REG_PQ_EN); +} + +static void mt8183_disp_pipeline_setup(u32 width, u32 height, + u32 frame_addr) +{ + void __iomem *ovl0 = (void __iomem *)DISP_OVL0_BASE; + void __iomem *ovl0_2l = (void __iomem *)DISP_OVL0_2L_BASE; + void __iomem *rdma0 = (void __iomem *)DISP_RDMA0_BASE; + void __iomem *color0 = (void __iomem *)DISP_COLOR0_BASE; + u32 pixel_clk = width * height * 60; + u32 threshold; + + /* + * ROI of the two overlays of the main path; OVL0 paints its + * background opaque (same background color as the coreboot + * port), OVL0_2L passes the stream through. + */ + writel(height << 16 | width, ovl0 + DISP_REG_OVL_ROI_SIZE); + writel(0xff0000ff, ovl0 + DISP_REG_OVL_ROI_BGCLR); + writel(height << 16 | width, ovl0_2l + DISP_REG_OVL_ROI_SIZE); + writel(0, ovl0_2l + DISP_REG_OVL_ROI_BGCLR); + + /* RDMA0: fetch size and FIFO thresholds. */ + writel(width, rdma0 + DISP_REG_RDMA_SIZE_CON_0); + writel(height, rdma0 + DISP_REG_RDMA_SIZE_CON_1); + /* + * Enable FIFO underflow since DSI can't be blocked. Set the + * output threshold to 6 microseconds with 7/6 overhead to + * account for blanking, and with a pixel depth of 4 bytes + * (from the coreboot rdma_config()). + */ + threshold = pixel_clk * 4 * 7 / 1000; + if (threshold > RDMA_FIFO_SIZE) + threshold = RDMA_FIFO_SIZE; + writel(RDMA_FIFO_UNDERFLOW_EN | + RDMA_FIFO_PSEUDO_SIZE(RDMA_FIFO_SIZE) | + RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold), + rdma0 + DISP_REG_RDMA_FIFO_CON); + writel(RDMA_ENGINE_EN, rdma0 + DISP_REG_RDMA_GLOBAL_CON); + + /* COLOR0: bypass all color processing. */ + writel(width, color0 + DISP_REG_COLOR_WIDTH); + writel(height, color0 + DISP_REG_COLOR_HEIGHT); + writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL, + color0 + DISP_REG_COLOR_CFG_MAIN); + writel(BIT(0), color0 + DISP_REG_COLOR_START); + + /* Post processing blocks: relay the stream through. */ + enable_pq((void __iomem *)DISP_CCORR0_BASE, width, height, 1); + enable_pq((void __iomem *)DISP_AAL0_BASE, width, height, 0); + enable_pq((void __iomem *)DISP_GAMMA0_BASE, width, height, 0); + enable_pq((void __iomem *)DISP_DITHER0_BASE, width, height, 1); + + /* Routing of the main path and its trigger (DSI vsync). */ + disp_config_main_path_connection(); + disp_config_main_path_mutex(); + + /* + * Overlay layer 0: the U-Boot framebuffer, 32-bit XRGB. The + * input format value is the one the coreboot port programs for + * the 32-bit framebuffer surface. + */ + writel(OVL_INFMT_RGBA8888 << 12, ovl0 + DISP_REG_OVL_L0_CON); + writel(height << 16 | width, ovl0 + DISP_REG_OVL_L0_SRC_SIZE); + writel((width * 4) & 0xffff, ovl0 + DISP_REG_OVL_L0_PITCH); + writel(frame_addr, ovl0 + DISP_REG_OVL_L0_ADDR); + writel(BIT(0), ovl0 + DISP_REG_OVL_L0_RDMA_CTRL); + writel(RDMA_MEM_GMC, ovl0 + DISP_REG_OVL_L0_RDMA_GMC); + writel(BIT(0), ovl0 + DISP_REG_OVL_SRC_CON); + + /* Select the background color of OVL0_2L from its input. */ + setbits_le32(ovl0_2l + DISP_REG_OVL_DATAPATH_CON, BIT(2)); + + /* + * The pass-through overlay must have no layers of its own: the + * boot firmware leaves its layer configuration in place across + * the handoff, and a stale OVL0_2L layer would blend leftover + * DRAM contents on top of the U-Boot framebuffer. + */ + writel(0, ovl0_2l + DISP_REG_OVL_SRC_CON); + + /* Enable the overlay engines. */ + writel(1, ovl0 + DISP_REG_OVL_EN); + writel(1, ovl0_2l + DISP_REG_OVL_EN); +} + +static int mt8183_display_bringup(struct udevice *dev, + struct video_uc_plat *plat, + struct video_priv *uc_priv, + const char **fail_stage) +{ + struct mt8183_display_priv *priv = dev_get_priv(dev); + struct mipi_dsi_panel_plat *mplat; + int stage, ret = 0; + + for (stage = STAGE_CLOCKS; stage < STAGE_COUNT && !ret; stage++) { + *fail_stage = stage_names[stage]; + + switch (stage) { + case STAGE_CLOCKS: + /* + * Power sequencing note: the display MTCMOS power + * domain (SCPSYS) is enabled by the boot firmware + * and stays on through the handoff; U-Boot has no + * SCPSYS driver for MT8183 to (re-)enable it. + */ + mt8183_disp_clock_on(); + break; + case STAGE_PANEL: + ret = uclass_first_device_err(UCLASS_PANEL, + &priv->panel); + if (ret) + break; + mplat = dev_get_plat(priv->panel); + + /* + * Retrieve the panel data link characteristics and + * publish the DSI device the panel driver sends its + * commands through. + */ + priv->device.dev = priv->panel; + priv->device.lanes = mplat->lanes; + priv->device.format = mplat->format; + priv->device.mode_flags = mplat->mode_flags; + mplat->device = &priv->device; + break; + case STAGE_DSI_INIT: + ret = uclass_get_device(UCLASS_DSI_HOST, 0, + &priv->dsi_host); + if (ret) + break; + ret = panel_get_display_timing(priv->panel, + &priv->timing); + if (ret) + break; + ret = dsi_host_init(priv->dsi_host, &priv->device, + &priv->timing, 4, NULL); + break; + case STAGE_DSI_ENABLE: + /* + * Runs the panel power sequence and the + * manufacturer init commands over DSI, in + * command mode. + */ + ret = panel_enable_backlight(priv->panel); + if (ret) + break; + /* Switch the host to video mode and stream. */ + ret = dsi_host_enable(priv->dsi_host); + break; + case STAGE_PIPELINE: + /* + * Allocate the framebuffer surface and make sure + * the OS never reuses it. A plain /memreserve/ + * entry is not an option: libfdt splices entries + * by shifting the structure block, which needs + * slack the relocated control FDT does not have + * (fdt_splice_() fails with -ENOSPC). Instead: + * + * - allocate the region as reserved through the + * EFI allocation API, which marks it in both + * LMB and the EFI memory map; the arm64 EFI + * stub derives the kernel memory layout from + * the latter, and + * - shrink the memory bank of the control FDT + * for kernels booted without EFI, replacing + * the reg property in place, which needs no + * slack. + */ + int mem; + + plat->base = FB_ADDR; + plat->size = priv->timing.hactive.typ * 4 * + priv->timing.vactive.typ; + + if (CONFIG_IS_ENABLED(EFI_LOADER)) { + u64 fb = plat->base; + + efi_allocate_pages(EFI_ALLOCATE_ADDRESS, + EFI_RESERVED_MEMORY_TYPE, + efi_size_in_pages(plat->size), + &fb); + } + + mem = fdt_path_offset((void *)gd->fdt_blob, + "/memory"); + if (mem >= 0) { + fdt64_t reg[2]; + + reg[0] = cpu_to_fdt64(DRAM_BASE); + reg[1] = cpu_to_fdt64(FB_ADDR - DRAM_BASE); + fdt_setprop((void *)gd->fdt_blob, mem, "reg", + reg, sizeof(reg)); + } + mmu_map_region(plat->base, + ALIGN(plat->size, SZ_4K), false); + + mt8183_disp_pipeline_setup(priv->timing.hactive.typ, + priv->timing.vactive.typ, + plat->base); + mt8183_disp_enable_backlight(); + break; + } + } + + if (ret) + return ret; + + uc_priv->bpix = VIDEO_BPP32; + uc_priv->xsize = priv->timing.hactive.typ; + uc_priv->ysize = priv->timing.vactive.typ; + uc_priv->line_length = uc_priv->xsize * 4; + uc_priv->rot = dev_read_u32_default(priv->panel, "rotation", 0) / 90; + + return 0; +} + +static int mt8183_display_probe(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + struct video_priv *uc_priv = dev_get_uclass_priv(dev); + const char *fail_stage = "start"; + fdt_addr_t ovl; + int ret; + + ovl = dev_read_addr(dev); + if (ovl == FDT_ADDR_T_NONE) + return log_msg_ret("ovl", -EINVAL); + + printf("[dsi] phase 0: bring-up start\n"); + ret = mt8183_display_bringup(dev, plat, uc_priv, &fail_stage); + if (!ret) { + printf("[dsi] phase 9: full bring-up done\n"); + printf("Video: MT8183 display %dx%d@32bpp at %llx (cold bring-up)\n", + uc_priv->xsize, uc_priv->ysize, + (unsigned long long)plat->base); + + /* + * The framebuffer is ordinary cacheable DRAM that the overlay + * reads through DMA: make video_sync() flush the dirty cache + * lines, or freshly drawn console output only becomes visible + * when the cache happens to evict them. + */ + video_set_flush_dcache(dev, true); + + return 0; + } + + /* + * Fall back to reviving the pipeline left running by the boot + * firmware. + */ + printf("[dsi] phase F: bring-up failed at stage %s (%d), falling back to firmware handoff\n", + fail_stage, ret); + ret = mt8183_disp_setup_handoff(ovl, plat, uc_priv); + if (ret) + return ret; + + printf("Video: MT8183 scanout %dx%d@32bpp at %llx (firmware handoff)\n", + uc_priv->xsize, uc_priv->ysize, + (unsigned long long)plat->base); + + video_set_flush_dcache(dev, true); + + return 0; +} + +static const struct udevice_id mt8183_display_ids[] = { + { .compatible = "mediatek,mt8183-disp-ovl" }, + { } +}; + +U_BOOT_DRIVER(mt8183_display) = { + .name = "mt8183_display", + .id = UCLASS_VIDEO, + .of_match = mt8183_display_ids, + .probe = mt8183_display_probe, + .priv_auto = sizeof(struct mt8183_display_priv), +}; + +/* + * TEMPORARY DIAGNOSTIC - DO NOT COMMIT. + * Prints an ASCII luminance map of the framebuffer (16x16 px blocks) + * over the console, in landscape orientation. + */ +static int do_fbmap(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *vid; + struct video_priv *priv; + u32 *fb; + int fx, fy, i, j, b, maxcol; + + uclass_first_device(UCLASS_VIDEO, &vid); + if (!vid) + return CMD_RET_FAILURE; + priv = dev_get_uclass_priv(vid); + fb = (u32 *)priv->fb; + + b = argc > 1 ? simple_strtoul(argv[1], NULL, 10) : 16; + maxcol = argc > 2 ? simple_strtoul(argv[2], NULL, 10) : priv->xsize; + if (b < 1 || b > 256 || maxcol < b || maxcol > priv->xsize) + return CMD_RET_USAGE; + + printf("fb %dx%d ll=%d rot=%d @p %08llx b=%d maxcol=%d\n", + priv->xsize, priv->ysize, priv->line_length, priv->rot, + (unsigned long long)(uintptr_t)priv->fb, b, maxcol); + + for (j = 0; j < priv->ysize / b; j++) { + for (i = 0; i < maxcol / b; i++) { + unsigned sum = 0; + char c; + + for (fy = 0; fy < b; fy++) + for (fx = 0; fx < b; fx++) + sum += (fb[(j * b + fy) * + priv->line_length / 4 + + i * b + fx] >> 8) & 0xff; + sum /= b * b; + c = sum < 16 ? '.' : sum < 64 ? '-' : sum < 128 ? '+' : '#'; + putc(c); + } + putc('\n'); + } + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(fbmap, 3, 1, do_fbmap, + "print ASCII luminance map of the framebuffer", + "[block_px] [max_col_px]"); diff --git a/drivers/video/mt8183_scanout.c b/drivers/video/mt8183_scanout.c index 8bb3f16bf3f..ecf5409f1a8 100644 --- a/drivers/video/mt8183_scanout.c +++ b/drivers/video/mt8183_scanout.c @@ -16,224 +16,31 @@ * 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. + * the video uclass so the standard vidconsole can render into it. See + * mt8183_disp.c for the shared handoff code and mt8183_display.c for a + * driver that brings the pipeline up from scratch instead. */ #include -#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; -} +#include "mt8183_disp.h" 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); + ret = mt8183_disp_setup_handoff(ovl, plat, uc_priv); 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 buffer was allocated by the boot firmware and is not - * tracked by U-Boot's memory management. Without a reservation it - * shows up as conventional RAM: once the kernel's page allocator - * goes live it reuses the region and the display corrupts. Record a - * memory reservation in the control FDT and feed it to LMB, so that - * EFI page allocations skip it, the EFI DT fixup marks it reserved - * in the memory map, and the kernel honors the /memreserve of the - * DTB it receives. - */ - ret = fdt_add_mem_rsv((void *)gd->fdt_blob, plat->base, plat->size); - if (ret) - log_warning("FDT reservation for scanout buffer failed: %d\n", - ret); - else - boot_fdt_add_mem_rsv_regions(gd->fdt_blob); - - /* 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; - - /* - * The BOE TV101WUM-NL6 panel is mounted rotated 270 degrees in - * the krane chassis (see the panel node's "rotation" property in - * the upstream DT). The keyboard-covered landscape orientation - * therefore needs the console rotated; the uclass then binds the - * rotated vidconsole3 text driver, which writes glyphs rotated - * by 270 degrees clockwise into the native 1200x1920 portrait - * scanout and swaps the console geometry (240x75 columns/rows - * on this panel). rot=1 (90 degrees) was tried first and read - * upside down on the device (R35). - */ - uc_priv->rot = 3; + return ret; printf("Video: MT8183 scanout %dx%d@32bpp at %llx\n", uc_priv->xsize, uc_priv->ysize, (unsigned long long)plat->base); diff --git a/drivers/video/mtk_dsi.c b/drivers/video/mtk_dsi.c index 332ac528fe7..8412ef0038c 100644 --- a/drivers/video/mtk_dsi.c +++ b/drivers/video/mtk_dsi.c @@ -223,14 +223,15 @@ static void mtk_dsi_phy_timing(struct mtk_dsi *dsi) static void mtk_dsi_reset(struct mtk_dsi *dsi) { /* - * Force an immediate commit through the MMSYS shadow registers, - * then pulse the DSI core reset, from mtk_dsi_reset() in the - * coreboot port. + * Enable the DSI core, then pulse the core reset while keeping + * the enable bit, like mtk_dsi_enable() + mtk_dsi_reset_engine() + * of the Linux driver. Writing CON_CTRL directly would clear + * DSI_EN and leave the engine disabled for the panel + * initialization commands that follow. */ - writel(DSI_FORCE_COMMIT_USE_MMSYS | DSI_FORCE_COMMIT_ALWAYS, - dsi->regs + DSI_FORCE_COMMIT); - writel(DSI_RESET, dsi->regs + DSI_CON_CTRL); - writel(0, dsi->regs + DSI_CON_CTRL); + setbits_le32(dsi->regs + DSI_CON_CTRL, DSI_EN); + setbits_le32(dsi->regs + DSI_CON_CTRL, DSI_RESET); + clrbits_le32(dsi->regs + DSI_CON_CTRL, DSI_RESET); } static void mtk_dsi_reset_dphy(struct mtk_dsi *dsi) @@ -420,6 +421,16 @@ static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host, } ret = mtk_dsi_wait_not_busy(dsi); + if (ret) { + /* + * The boot firmware may have left the engine in a state + * where polling alone never recovers; re-enable and reset + * the core like mtk_dsi_wait_for_idle() of the Linux + * driver, then try once more. + */ + mtk_dsi_reset(dsi); + ret = mtk_dsi_wait_not_busy(dsi); + } if (ret) { dev_err(host->dev, "DSI busy, cannot send command\n"); goto out; diff --git a/drivers/video/panel_boe_tv101wum.c b/drivers/video/panel_boe_tv101wum.c new file mode 100644 index 00000000000..12c622415fa --- /dev/null +++ b/drivers/video/panel_boe_tv101wum.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BOE TV101WUM-NL6 MIPI DSI panel driver. + * + * Copyright (C) 2026 Vincent Haudiquet + * + * 1200x1920 (native portrait) video-mode panel, 4 data lanes, RGB888, + * mounted rotated in the krane (Lenovo IdeaPad Duet) chassis. + * + * The power sequence and the manufacturer initialization commands are + * ported from the ChromeOS coreboot port for this exact panel, + * src/mainboard/google/kukui/panel_params/panel-BOE_TV101WUM_NL6.c in + * coreboot 4.14, which matches the sequence of the Linux kernel driver + * drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c for the + * "boe,tv101wum-nl6" entry (159.425 MHz pixel clock, 100/40/24 + * horizontal and 10/14/4 vertical timing). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Manufacturer initialization command stream. Packed as + * [ [...]] + * with type one of: + * LCM_DELAY_CMD: + * LCM_DCS_CMD: + * Ported verbatim from the coreboot panel-BOE_TV101WUM_NL6.c table. + */ +#define LCM_END_CMD 0 +#define LCM_DELAY_CMD 1 +#define LCM_DCS_CMD 3 + +#define LCM_DELAY(ms) LCM_DELAY_CMD, (ms) +#define LCM_DCS_CMD_ENTRY(seq...) \ + LCM_DCS_CMD, LCM_NPARAM(seq), seq +#define LCM_NPARAM(...) \ + (sizeof((u8[]){0, ##__VA_ARGS__}) / sizeof(u8) - 1) + +static const u8 boe_tv101wum_nl6_init_seq[] = { + LCM_DELAY(24), + LCM_DCS_CMD_ENTRY(0xB0, 0x05), + LCM_DCS_CMD_ENTRY(0xB1, 0xE5), + LCM_DCS_CMD_ENTRY(0xB3, 0x52), + LCM_DCS_CMD_ENTRY(0xB0, 0x00), + LCM_DCS_CMD_ENTRY(0xB3, 0x88), + LCM_DCS_CMD_ENTRY(0xB0, 0x04), + LCM_DCS_CMD_ENTRY(0xB8, 0x00), + LCM_DCS_CMD_ENTRY(0xB0, 0x00), + LCM_DCS_CMD_ENTRY(0xB6, 0x03), + LCM_DCS_CMD_ENTRY(0xBA, 0x8B), + LCM_DCS_CMD_ENTRY(0xBF, 0x1A), + LCM_DCS_CMD_ENTRY(0xC0, 0x0F), + LCM_DCS_CMD_ENTRY(0xC2, 0x0C), + LCM_DCS_CMD_ENTRY(0xC3, 0x02), + LCM_DCS_CMD_ENTRY(0xC4, 0x0C), + LCM_DCS_CMD_ENTRY(0xC5, 0x02), + LCM_DCS_CMD_ENTRY(0xB0, 0x01), + LCM_DCS_CMD_ENTRY(0xE0, 0x26), + LCM_DCS_CMD_ENTRY(0xE1, 0x26), + LCM_DCS_CMD_ENTRY(0xDC, 0x00), + LCM_DCS_CMD_ENTRY(0xDD, 0x00), + LCM_DCS_CMD_ENTRY(0xCC, 0x26), + LCM_DCS_CMD_ENTRY(0xCD, 0x26), + LCM_DCS_CMD_ENTRY(0xC8, 0x00), + LCM_DCS_CMD_ENTRY(0xC9, 0x00), + LCM_DCS_CMD_ENTRY(0xD2, 0x03), + LCM_DCS_CMD_ENTRY(0xD3, 0x03), + LCM_DCS_CMD_ENTRY(0xE6, 0x04), + LCM_DCS_CMD_ENTRY(0xE7, 0x04), + LCM_DCS_CMD_ENTRY(0xC4, 0x09), + LCM_DCS_CMD_ENTRY(0xC5, 0x09), + LCM_DCS_CMD_ENTRY(0xD8, 0x0A), + LCM_DCS_CMD_ENTRY(0xD9, 0x0A), + LCM_DCS_CMD_ENTRY(0xC2, 0x0B), + LCM_DCS_CMD_ENTRY(0xC3, 0x0B), + LCM_DCS_CMD_ENTRY(0xD6, 0x0C), + LCM_DCS_CMD_ENTRY(0xD7, 0x0C), + LCM_DCS_CMD_ENTRY(0xC0, 0x05), + LCM_DCS_CMD_ENTRY(0xC1, 0x05), + LCM_DCS_CMD_ENTRY(0xD4, 0x06), + LCM_DCS_CMD_ENTRY(0xD5, 0x06), + LCM_DCS_CMD_ENTRY(0xCA, 0x07), + LCM_DCS_CMD_ENTRY(0xCB, 0x07), + LCM_DCS_CMD_ENTRY(0xDE, 0x08), + LCM_DCS_CMD_ENTRY(0xDF, 0x08), + LCM_DCS_CMD_ENTRY(0xB0, 0x02), + LCM_DCS_CMD_ENTRY(0xC0, 0x00), + LCM_DCS_CMD_ENTRY(0xC1, 0x0D), + LCM_DCS_CMD_ENTRY(0xC2, 0x17), + LCM_DCS_CMD_ENTRY(0xC3, 0x26), + LCM_DCS_CMD_ENTRY(0xC4, 0x31), + LCM_DCS_CMD_ENTRY(0xC5, 0x1C), + LCM_DCS_CMD_ENTRY(0xC6, 0x2C), + LCM_DCS_CMD_ENTRY(0xC7, 0x33), + LCM_DCS_CMD_ENTRY(0xC8, 0x31), + LCM_DCS_CMD_ENTRY(0xC9, 0x37), + LCM_DCS_CMD_ENTRY(0xCA, 0x37), + LCM_DCS_CMD_ENTRY(0xCB, 0x37), + LCM_DCS_CMD_ENTRY(0xCC, 0x39), + LCM_DCS_CMD_ENTRY(0xCD, 0x2E), + LCM_DCS_CMD_ENTRY(0xCE, 0x2F), + LCM_DCS_CMD_ENTRY(0xCF, 0x2F), + LCM_DCS_CMD_ENTRY(0xD0, 0x07), + LCM_DCS_CMD_ENTRY(0xD2, 0x00), + LCM_DCS_CMD_ENTRY(0xD3, 0x0D), + LCM_DCS_CMD_ENTRY(0xD4, 0x17), + LCM_DCS_CMD_ENTRY(0xD5, 0x26), + LCM_DCS_CMD_ENTRY(0xD6, 0x31), + LCM_DCS_CMD_ENTRY(0xD7, 0x3F), + LCM_DCS_CMD_ENTRY(0xD8, 0x3F), + LCM_DCS_CMD_ENTRY(0xD9, 0x3F), + LCM_DCS_CMD_ENTRY(0xDA, 0x3F), + LCM_DCS_CMD_ENTRY(0xDB, 0x37), + LCM_DCS_CMD_ENTRY(0xDC, 0x37), + LCM_DCS_CMD_ENTRY(0xDD, 0x37), + LCM_DCS_CMD_ENTRY(0xDE, 0x39), + LCM_DCS_CMD_ENTRY(0xDF, 0x2E), + LCM_DCS_CMD_ENTRY(0xE0, 0x2F), + LCM_DCS_CMD_ENTRY(0xE1, 0x2F), + LCM_DCS_CMD_ENTRY(0xE2, 0x07), + LCM_DCS_CMD_ENTRY(0xB0, 0x03), + LCM_DCS_CMD_ENTRY(0xC8, 0x0B), + LCM_DCS_CMD_ENTRY(0xC9, 0x07), + LCM_DCS_CMD_ENTRY(0xC3, 0x00), + LCM_DCS_CMD_ENTRY(0xE7, 0x00), + LCM_DCS_CMD_ENTRY(0xC5, 0x2A), + LCM_DCS_CMD_ENTRY(0xDE, 0x2A), + LCM_DCS_CMD_ENTRY(0xCA, 0x43), + LCM_DCS_CMD_ENTRY(0xC9, 0x07), + LCM_DCS_CMD_ENTRY(0xE4, 0xC0), + LCM_DCS_CMD_ENTRY(0xE5, 0x0D), + LCM_DCS_CMD_ENTRY(0xCB, 0x00), + LCM_DCS_CMD_ENTRY(0xB0, 0x06), + LCM_DCS_CMD_ENTRY(0xB8, 0xA5), + LCM_DCS_CMD_ENTRY(0xC0, 0xA5), + LCM_DCS_CMD_ENTRY(0xC7, 0x0F), + LCM_DCS_CMD_ENTRY(0xD5, 0x32), + LCM_DCS_CMD_ENTRY(0xB8, 0x00), + LCM_DCS_CMD_ENTRY(0xC0, 0x00), + LCM_DCS_CMD_ENTRY(0xBC, 0x00), + LCM_DCS_CMD_ENTRY(0xB0, 0x07), + LCM_DCS_CMD_ENTRY(0xB1, 0x00), + LCM_DCS_CMD_ENTRY(0xB2, 0x02), + LCM_DCS_CMD_ENTRY(0xB3, 0x0F), + LCM_DCS_CMD_ENTRY(0xB4, 0x25), + LCM_DCS_CMD_ENTRY(0xB5, 0x39), + LCM_DCS_CMD_ENTRY(0xB6, 0x4E), + LCM_DCS_CMD_ENTRY(0xB7, 0x72), + LCM_DCS_CMD_ENTRY(0xB8, 0x97), + LCM_DCS_CMD_ENTRY(0xB9, 0xDC), + LCM_DCS_CMD_ENTRY(0xBA, 0x22), + LCM_DCS_CMD_ENTRY(0xBB, 0xA4), + LCM_DCS_CMD_ENTRY(0xBC, 0x2B), + LCM_DCS_CMD_ENTRY(0xBD, 0x2F), + LCM_DCS_CMD_ENTRY(0xBE, 0xA9), + LCM_DCS_CMD_ENTRY(0xBF, 0x25), + LCM_DCS_CMD_ENTRY(0xC0, 0x61), + LCM_DCS_CMD_ENTRY(0xC1, 0x97), + LCM_DCS_CMD_ENTRY(0xC2, 0xB2), + LCM_DCS_CMD_ENTRY(0xC3, 0xCD), + LCM_DCS_CMD_ENTRY(0xC4, 0xD9), + LCM_DCS_CMD_ENTRY(0xC5, 0xE7), + LCM_DCS_CMD_ENTRY(0xC6, 0xF4), + LCM_DCS_CMD_ENTRY(0xC7, 0xFA), + LCM_DCS_CMD_ENTRY(0xC8, 0xFC), + LCM_DCS_CMD_ENTRY(0xC9, 0x00), + LCM_DCS_CMD_ENTRY(0xCA, 0x00), + LCM_DCS_CMD_ENTRY(0xCB, 0x16), + LCM_DCS_CMD_ENTRY(0xCC, 0xAF), + LCM_DCS_CMD_ENTRY(0xCD, 0xFF), + LCM_DCS_CMD_ENTRY(0xCE, 0xFF), + LCM_DCS_CMD_ENTRY(0xB0, 0x08), + LCM_DCS_CMD_ENTRY(0xB1, 0x04), + LCM_DCS_CMD_ENTRY(0xB2, 0x05), + LCM_DCS_CMD_ENTRY(0xB3, 0x11), + LCM_DCS_CMD_ENTRY(0xB4, 0x24), + LCM_DCS_CMD_ENTRY(0xB5, 0x39), + LCM_DCS_CMD_ENTRY(0xB6, 0x4F), + LCM_DCS_CMD_ENTRY(0xB7, 0x72), + LCM_DCS_CMD_ENTRY(0xB8, 0x98), + LCM_DCS_CMD_ENTRY(0xB9, 0xDC), + LCM_DCS_CMD_ENTRY(0xBA, 0x23), + LCM_DCS_CMD_ENTRY(0xBB, 0xA6), + LCM_DCS_CMD_ENTRY(0xBC, 0x2C), + LCM_DCS_CMD_ENTRY(0xBD, 0x30), + LCM_DCS_CMD_ENTRY(0xBE, 0xAA), + LCM_DCS_CMD_ENTRY(0xBF, 0x26), + LCM_DCS_CMD_ENTRY(0xC0, 0x62), + LCM_DCS_CMD_ENTRY(0xC1, 0x9B), + LCM_DCS_CMD_ENTRY(0xC2, 0xB5), + LCM_DCS_CMD_ENTRY(0xC3, 0xCF), + LCM_DCS_CMD_ENTRY(0xC4, 0xDB), + LCM_DCS_CMD_ENTRY(0xC5, 0xE8), + LCM_DCS_CMD_ENTRY(0xC6, 0xF5), + LCM_DCS_CMD_ENTRY(0xC7, 0xFA), + LCM_DCS_CMD_ENTRY(0xC8, 0xFC), + LCM_DCS_CMD_ENTRY(0xC9, 0x00), + LCM_DCS_CMD_ENTRY(0xCA, 0x00), + LCM_DCS_CMD_ENTRY(0xCB, 0x16), + LCM_DCS_CMD_ENTRY(0xCC, 0xAF), + LCM_DCS_CMD_ENTRY(0xCD, 0xFF), + LCM_DCS_CMD_ENTRY(0xCE, 0xFF), + LCM_DCS_CMD_ENTRY(0xB0, 0x09), + LCM_DCS_CMD_ENTRY(0xB1, 0x04), + LCM_DCS_CMD_ENTRY(0xB2, 0x02), + LCM_DCS_CMD_ENTRY(0xB3, 0x16), + LCM_DCS_CMD_ENTRY(0xB4, 0x24), + LCM_DCS_CMD_ENTRY(0xB5, 0x3B), + LCM_DCS_CMD_ENTRY(0xB6, 0x4F), + LCM_DCS_CMD_ENTRY(0xB7, 0x73), + LCM_DCS_CMD_ENTRY(0xB8, 0x99), + LCM_DCS_CMD_ENTRY(0xB9, 0xE0), + LCM_DCS_CMD_ENTRY(0xBA, 0x26), + LCM_DCS_CMD_ENTRY(0xBB, 0xAD), + LCM_DCS_CMD_ENTRY(0xBC, 0x36), + LCM_DCS_CMD_ENTRY(0xBD, 0x3A), + LCM_DCS_CMD_ENTRY(0xBE, 0xAE), + LCM_DCS_CMD_ENTRY(0xBF, 0x2A), + LCM_DCS_CMD_ENTRY(0xC0, 0x66), + LCM_DCS_CMD_ENTRY(0xC1, 0x9E), + LCM_DCS_CMD_ENTRY(0xC2, 0xB8), + LCM_DCS_CMD_ENTRY(0xC3, 0xD1), + LCM_DCS_CMD_ENTRY(0xC4, 0xDD), + LCM_DCS_CMD_ENTRY(0xC5, 0xE9), + LCM_DCS_CMD_ENTRY(0xC6, 0xF6), + LCM_DCS_CMD_ENTRY(0xC7, 0xFA), + LCM_DCS_CMD_ENTRY(0xC8, 0xFC), + LCM_DCS_CMD_ENTRY(0xC9, 0x00), + LCM_DCS_CMD_ENTRY(0xCA, 0x00), + LCM_DCS_CMD_ENTRY(0xCB, 0x16), + LCM_DCS_CMD_ENTRY(0xCC, 0xAF), + LCM_DCS_CMD_ENTRY(0xCD, 0xFF), + LCM_DCS_CMD_ENTRY(0xCE, 0xFF), + LCM_DCS_CMD_ENTRY(0xB0, 0x0A), + LCM_DCS_CMD_ENTRY(0xB1, 0x00), + LCM_DCS_CMD_ENTRY(0xB2, 0x02), + LCM_DCS_CMD_ENTRY(0xB3, 0x0F), + LCM_DCS_CMD_ENTRY(0xB4, 0x25), + LCM_DCS_CMD_ENTRY(0xB5, 0x39), + LCM_DCS_CMD_ENTRY(0xB6, 0x4E), + LCM_DCS_CMD_ENTRY(0xB7, 0x72), + LCM_DCS_CMD_ENTRY(0xB8, 0x97), + LCM_DCS_CMD_ENTRY(0xB9, 0xDC), + LCM_DCS_CMD_ENTRY(0xBA, 0x22), + LCM_DCS_CMD_ENTRY(0xBB, 0xA4), + LCM_DCS_CMD_ENTRY(0xBC, 0x2B), + LCM_DCS_CMD_ENTRY(0xBD, 0x2F), + LCM_DCS_CMD_ENTRY(0xBE, 0xA9), + LCM_DCS_CMD_ENTRY(0xBF, 0x25), + LCM_DCS_CMD_ENTRY(0xC0, 0x61), + LCM_DCS_CMD_ENTRY(0xC1, 0x97), + LCM_DCS_CMD_ENTRY(0xC2, 0xB2), + LCM_DCS_CMD_ENTRY(0xC3, 0xCD), + LCM_DCS_CMD_ENTRY(0xC4, 0xD9), + LCM_DCS_CMD_ENTRY(0xC5, 0xE7), + LCM_DCS_CMD_ENTRY(0xC6, 0xF4), + LCM_DCS_CMD_ENTRY(0xC7, 0xFA), + LCM_DCS_CMD_ENTRY(0xC8, 0xFC), + LCM_DCS_CMD_ENTRY(0xC9, 0x00), + LCM_DCS_CMD_ENTRY(0xCA, 0x00), + LCM_DCS_CMD_ENTRY(0xCB, 0x16), + LCM_DCS_CMD_ENTRY(0xCC, 0xAF), + LCM_DCS_CMD_ENTRY(0xCD, 0xFF), + LCM_DCS_CMD_ENTRY(0xCE, 0xFF), + LCM_DCS_CMD_ENTRY(0xB0, 0x0B), + LCM_DCS_CMD_ENTRY(0xB1, 0x04), + LCM_DCS_CMD_ENTRY(0xB2, 0x05), + LCM_DCS_CMD_ENTRY(0xB3, 0x11), + LCM_DCS_CMD_ENTRY(0xB4, 0x24), + LCM_DCS_CMD_ENTRY(0xB5, 0x39), + LCM_DCS_CMD_ENTRY(0xB6, 0x4F), + LCM_DCS_CMD_ENTRY(0xB7, 0x72), + LCM_DCS_CMD_ENTRY(0xB8, 0x98), + LCM_DCS_CMD_ENTRY(0xB9, 0xDC), + LCM_DCS_CMD_ENTRY(0xBA, 0x23), + LCM_DCS_CMD_ENTRY(0xBB, 0xA6), + LCM_DCS_CMD_ENTRY(0xBC, 0x2C), + LCM_DCS_CMD_ENTRY(0xBD, 0x30), + LCM_DCS_CMD_ENTRY(0xBE, 0xAA), + LCM_DCS_CMD_ENTRY(0xBF, 0x26), + LCM_DCS_CMD_ENTRY(0xC0, 0x62), + LCM_DCS_CMD_ENTRY(0xC1, 0x9B), + LCM_DCS_CMD_ENTRY(0xC2, 0xB5), + LCM_DCS_CMD_ENTRY(0xC3, 0xCF), + LCM_DCS_CMD_ENTRY(0xC4, 0xDB), + LCM_DCS_CMD_ENTRY(0xC5, 0xE8), + LCM_DCS_CMD_ENTRY(0xC6, 0xF5), + LCM_DCS_CMD_ENTRY(0xC7, 0xFA), + LCM_DCS_CMD_ENTRY(0xC8, 0xFC), + LCM_DCS_CMD_ENTRY(0xC9, 0x00), + LCM_DCS_CMD_ENTRY(0xCA, 0x00), + LCM_DCS_CMD_ENTRY(0xCB, 0x16), + LCM_DCS_CMD_ENTRY(0xCC, 0xAF), + LCM_DCS_CMD_ENTRY(0xCD, 0xFF), + LCM_DCS_CMD_ENTRY(0xCE, 0xFF), + LCM_DCS_CMD_ENTRY(0xB0, 0x0C), + LCM_DCS_CMD_ENTRY(0xB1, 0x04), + LCM_DCS_CMD_ENTRY(0xB2, 0x02), + LCM_DCS_CMD_ENTRY(0xB3, 0x16), + LCM_DCS_CMD_ENTRY(0xB4, 0x24), + LCM_DCS_CMD_ENTRY(0xB5, 0x3B), + LCM_DCS_CMD_ENTRY(0xB6, 0x4F), + LCM_DCS_CMD_ENTRY(0xB7, 0x73), + LCM_DCS_CMD_ENTRY(0xB8, 0x99), + LCM_DCS_CMD_ENTRY(0xB9, 0xE0), + LCM_DCS_CMD_ENTRY(0xBA, 0x26), + LCM_DCS_CMD_ENTRY(0xBB, 0xAD), + LCM_DCS_CMD_ENTRY(0xBC, 0x36), + LCM_DCS_CMD_ENTRY(0xBD, 0x3A), + LCM_DCS_CMD_ENTRY(0xBE, 0xAE), + LCM_DCS_CMD_ENTRY(0xBF, 0x2A), + LCM_DCS_CMD_ENTRY(0xC0, 0x66), + LCM_DCS_CMD_ENTRY(0xC1, 0x9E), + LCM_DCS_CMD_ENTRY(0xC2, 0xB8), + LCM_DCS_CMD_ENTRY(0xC3, 0xD1), + LCM_DCS_CMD_ENTRY(0xC4, 0xDD), + LCM_DCS_CMD_ENTRY(0xC5, 0xE9), + LCM_DCS_CMD_ENTRY(0xC6, 0xF6), + LCM_DCS_CMD_ENTRY(0xC7, 0xFA), + LCM_DCS_CMD_ENTRY(0xC8, 0xFC), + LCM_DCS_CMD_ENTRY(0xC9, 0x00), + LCM_DCS_CMD_ENTRY(0xCA, 0x00), + LCM_DCS_CMD_ENTRY(0xCB, 0x16), + LCM_DCS_CMD_ENTRY(0xCC, 0xAF), + LCM_DCS_CMD_ENTRY(0xCD, 0xFF), + LCM_DCS_CMD_ENTRY(0xCE, 0xFF), + LCM_DCS_CMD_ENTRY(0xB0, 0x00), + LCM_DCS_CMD_ENTRY(0xB3, 0x08), + LCM_DCS_CMD_ENTRY(0xB0, 0x04), + LCM_DCS_CMD_ENTRY(0xB8, 0x68), + LCM_DELAY(150), + LCM_END_CMD, +}; + +/* + * Display timing. Totals match the Linux driver's + * boe_tv101wum_nl6_default_mode (htotal 1364, vtotal 1948), but the + * sync/back-porch split follows the coreboot EDID for this panel + * (hso 100 / hspw 24 / hbl-hso-hspw 40; vso 10 / vspw 4 / 14), the + * configuration proven to drive this panel on this device. + */ +static const struct display_timing boe_tv101wum_nl6_timing = { + .pixelclock.typ = 159425000, + .hactive.typ = 1200, + .hfront_porch.typ = 100, + .hback_porch.typ = 40, + .hsync_len.typ = 24, + .vactive.typ = 1920, + .vfront_porch.typ = 10, + .vback_porch.typ = 14, + .vsync_len.typ = 4, +}; + +struct boe_panel_priv { + struct udevice *avdd; + struct udevice *avee; + struct udevice *pp1800; + struct gpio_desc enable; +}; + +static int boe_panel_send_init_sequence(struct udevice *dev) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + struct mipi_dsi_device *device = plat->device; + const u8 *p = boe_tv101wum_nl6_init_seq; + int ret; + + while (*p != LCM_END_CMD) { + u8 type = *p++; + u8 len; + + switch (type) { + case LCM_DELAY_CMD: + mdelay(*p++); + continue; + case LCM_DCS_CMD: + break; + default: + dev_err(dev, "unknown command type %u\n", type); + return -EINVAL; + } + + len = *p++; + ret = mipi_dsi_dcs_write_buffer(device, p, len); + if (ret < 0) + return ret; + p += len; + } + + return 0; +} + +static int boe_panel_enable_backlight(struct udevice *dev) +{ + struct boe_panel_priv *priv = dev_get_priv(dev); + int ret; + + /* + * Power sequence from boe_panel_prepare() of the Linux driver + * (regulators on, reset pulse, manufacturer init commands). On + * the kukui family the supplies are already enabled by the boot + * firmware; enabling them again is a no-op. + */ + if (CONFIG_IS_ENABLED(DM_REGULATOR)) { + if (priv->avdd) { + ret = regulator_set_enable_if_allowed(priv->avdd, + true); + if (ret && ret != -ENOSYS) + return ret; + } + if (priv->avee) { + ret = regulator_set_enable_if_allowed(priv->avee, + true); + if (ret && ret != -ENOSYS) + return ret; + } + if (priv->pp1800) { + ret = regulator_set_enable_if_allowed(priv->pp1800, + true); + if (ret && ret != -ENOSYS) + return ret; + } + } + udelay(10000); + + /* + * Reset pulse, following boe_panel_prepare() of the Linux driver: + * the "enable-gpios" pin is the active-low reset, so the sequence + * ends with it de-asserted (logical 1) before the init commands. + */ + if (dm_gpio_is_valid(&priv->enable)) { + dm_gpio_set_value(&priv->enable, 1); + udelay(1500); + dm_gpio_set_value(&priv->enable, 0); + udelay(1500); + dm_gpio_set_value(&priv->enable, 1); + } + udelay(8000); + + ret = boe_panel_send_init_sequence(dev); + if (ret) { + dev_err(dev, "failed to send init sequence: %d\n", ret); + return ret; + } + + /* Enable the backlight after the 150 ms delay of the sequence. */ + if (CONFIG_IS_ENABLED(BACKLIGHT)) { + struct udevice *backlight; + + if (!uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, + "backlight", &backlight)) + backlight_enable(backlight); + } + + return 0; +} + +static int boe_panel_get_display_timing(struct udevice *dev, + struct display_timing *timings) +{ + memcpy(timings, &boe_tv101wum_nl6_timing, sizeof(*timings)); + + return 0; +} + +static int boe_panel_of_to_plat(struct udevice *dev) +{ + struct boe_panel_priv *priv = dev_get_priv(dev); + int ret; + + if (CONFIG_IS_ENABLED(DM_REGULATOR)) { + ret = device_get_supply_regulator(dev, "avdd-supply", + &priv->avdd); + if (ret && ret != -ENOENT) + return ret; + ret = device_get_supply_regulator(dev, "avee-supply", + &priv->avee); + if (ret && ret != -ENOENT) + return ret; + ret = device_get_supply_regulator(dev, "pp1800-supply", + &priv->pp1800); + if (ret && ret != -ENOENT) + return ret; + } + + ret = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable, + GPIOD_IS_OUT); + if (ret && ret != -ENOENT) { + dev_err(dev, "cannot get enable GPIO: %d\n", ret); + return ret; + } + + /* + * Default to the reset released: a failed bring-up falls back to + * the firmware-handoff revival, which needs the panel alive. + */ + if (dm_gpio_is_valid(&priv->enable)) + dm_gpio_set_value(&priv->enable, 1); + + return 0; +} + +static int boe_panel_probe(struct udevice *dev) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + + /* Fill the DSI data link characteristics for the host. */ + plat->lanes = 4; + plat->format = MIPI_DSI_FMT_RGB888; + plat->mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct panel_ops boe_panel_ops = { + .enable_backlight = boe_panel_enable_backlight, + .get_display_timing = boe_panel_get_display_timing, +}; + +static const struct udevice_id boe_panel_ids[] = { + { .compatible = "boe,tv101wum-nl6" }, + { } +}; + +U_BOOT_DRIVER(boe_panel) = { + .name = "boe_panel", + .id = UCLASS_PANEL, + .of_match = boe_panel_ids, + .ops = &boe_panel_ops, + .of_to_plat = boe_panel_of_to_plat, + .probe = boe_panel_probe, + .plat_auto = sizeof(struct mipi_dsi_panel_plat), + .priv_auto = sizeof(struct boe_panel_priv), +}; diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c index 8d076058280..2cf7ab3f231 100644 --- a/lib/efi_loader/efi_console.c +++ b/lib/efi_loader/efi_console.c @@ -194,6 +194,14 @@ static efi_status_t EFIAPI efi_cout_output_string( } pos = buf; utf16_utf8_strcpy(&pos, string); + /* + * Output printed by U-Boot itself moves the video console cursor + * without updating the position tracked for EFI. Re-sync the console + * cursor before printing so text appears where the EFI spec places it. + */ + if (IS_ENABLED(CONFIG_VIDEO)) + printf(ESC "[%d;%dH", (int)con->cursor_row + 1, + (int)con->cursor_column + 1); puts(buf); free(buf); @@ -344,7 +352,8 @@ static int __maybe_unused query_vidconsole(int *rows, int *cols) struct udevice *dev; struct vidconsole_priv *priv; - if (!stdout_name || strncmp(stdout_name, "vidconsole", 10)) + /* stdout may be a comma-separated list, e.g. "serial,vidconsole" */ + if (!stdout_name || !strstr(stdout_name, "vidconsole")) return -ENODEV; stdout_dev = stdio_get_by_name("vidconsole"); if (!stdout_dev) @@ -370,11 +379,14 @@ void efi_setup_console_size(void) if (IS_ENABLED(CONFIG_VIDEO)) ret = query_vidconsole(&rows, &cols); + printf("EFI console: vidquery ret=%d, size %dx%d\n", ret, cols, rows); if (ret) { if (no_ansi) ret = 0; else ret = query_console_serial(&rows, &cols); + printf("EFI console: serial fallback ret=%d, size %dx%d\n", + ret, cols, rows); } if (ret) return; @@ -399,6 +411,14 @@ void efi_setup_console_size(void) efi_con_mode.max_mode = EFI_MAX_COUT_MODE; efi_con_mode.mode = EFI_COUT_MODE_2; } + + /* + * EFI applications expect output to start at the top left of the + * screen, while the U-Boot prompt may have left the cursor anywhere. + */ + printf(ESC "[1;1H"); + efi_con_mode.cursor_column = 0; + efi_con_mode.cursor_row = 0; } /** diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c index c3da7c20cb2..6453a8ec655 100644 --- a/lib/efi_loader/efi_memory.c +++ b/lib/efi_loader/efi_memory.c @@ -406,6 +406,21 @@ efi_status_t efi_add_memory_map(u64 start, u64 size, int memory_type) * @must_be_allocated: return success if the page is allocated * Return: status code */ +/* TEMPORARY DIAGNOSTIC - DO NOT COMMIT */ +static void efi_mem_dump_diag(void) +{ + struct efi_mem_list *lmem; + int i = 0; + + list_for_each_entry(lmem, &efi_mem, link) { + log_err("EFI map[%d]: type %d start %08llx pages %llu attr %llx\n", + i++, lmem->desc.type, + lmem->desc.physical_start, + lmem->desc.num_pages, + lmem->desc.attribute); + } +} + static efi_status_t efi_check_allocated(u64 addr, bool must_be_allocated) { struct efi_mem_list *item; @@ -463,8 +478,12 @@ efi_status_t efi_allocate_pages(enum efi_allocate_type type, /* Any page */ err = lmb_alloc_mem(LMB_MEM_ALLOC_ANY, EFI_PAGE_SIZE, &addr, len, flags); - if (err) + if (err) { + log_err("EFI alloc %llu pages type %d: lmb_alloc_mem failed (%d)\n", + (u64)pages, memory_type, err); + efi_mem_dump_diag(); return EFI_OUT_OF_RESOURCES; + } break; case EFI_ALLOCATE_MAX_ADDRESS: /* Max address */ @@ -493,6 +512,10 @@ efi_status_t efi_allocate_pages(enum efi_allocate_type type, ret = efi_update_memory_map(efi_addr, pages, memory_type, true, false); if (ret != EFI_SUCCESS) { /* Map would overlap, bail out */ + log_err("EFI alloc %llu pages type %d at %08llx: map update failed (%lu)\n", + (u64)pages, memory_type, efi_addr, + ret & ~EFI_ERROR_MASK); + efi_mem_dump_diag(); lmb_free(addr, (u64)pages << EFI_PAGE_SHIFT, flags); unmap_sysmem((void *)(uintptr_t)efi_addr); if (type == EFI_ALLOCATE_ADDRESS)