video: add MT8183 cold display bring-up for krane with BOE panel

Bring up the krane (MT8183) display from a cold start in U-Boot,
replacing the depthcharge scanout revival approach:

- mt8183_display.c: OVL pipeline setup, staged bring-up (clocks,
  panel, DSI init, DSI enable, pipeline), framebuffer reservation
  in LMB + EFI map and control-FDT memory trim, dcache flush for
  the DMA-scanned framebuffer, rotated console via panel rotation
- mt8183_disp.c/h: OVL/mutex/MMSYS register helpers, handoff
  scanout discovery
- panel_boe_tv101wum.c: BOE TV101WUM panel driver with init
  sequence
- mtk_dsi: enable the DSI core across reset pulses and recover
  from a busy engine left behind by the boot firmware
- clk-mt8183: map legacy clock IDs to gate positions
- console rotate: fix scrolling on rotated consoles
- efi_memory/efi_console: allocation diagnostics and console
  fixes (WIP)
- MAINTAINERS, krane defconfig and serial updates

Verified on hardware: cold bring-up, EFI boot to grub and kernel.
This commit is contained in:
Vincent Haudiquet
2026-09-02 00:25:07 +02:00
parent 3bb613ee2f
commit 3fd015cd23
14 changed files with 1547 additions and 213 deletions
+4
View File
@@ -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
+6 -3
View File
@@ -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
+32
View File
@@ -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),
};
+11
View File
@@ -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);
+37 -1
View File
@@ -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.
+3 -1
View File
@@ -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
+175
View File
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek MT8183 display helpers shared by the display drivers.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* 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 <dm.h>
#include <image.h>
#include <video.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/unaligned.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/sizes.h>
#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;
}
+89
View File
@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MediaTek MT8183 display helpers shared by the display drivers.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*/
#ifndef _MT8183_DISP_H
#define _MT8183_DISP_H
#include <video.h>
/*
* 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
+577
View File
@@ -0,0 +1,577 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek MT8183 display driver: full pipeline bring-up.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* 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 <command.h>
#include <dm.h>
#include <dsi_host.h>
#include <efi_loader.h>
#include <fdt_support.h>
#include <image.h>
#include <panel.h>
#include <video.h>
#include <asm/io.h>
#include <asm/system.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/sizes.h>
#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]");
+6 -199
View File
@@ -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 <dm.h>
#include <image.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;
}
#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);
+18 -7
View File
@@ -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;
+544
View File
@@ -0,0 +1,544 @@
// SPDX-License-Identifier: GPL-2.0
/*
* BOE TV101WUM-NL6 MIPI DSI panel driver.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* 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 <backlight.h>
#include <dm.h>
#include <mipi_dsi.h>
#include <panel.h>
#include <asm/gpio.h>
#include <dm/device_compat.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <power/regulator.h>
/*
* Manufacturer initialization command stream. Packed as
* <type> [<len> [<param>...]]
* with type one of:
* LCM_DELAY_CMD: <type> <milliseconds>
* LCM_DCS_CMD: <type> <len> <parameters...>
* 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),
};
+21 -1
View File
@@ -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;
}
/**
+24 -1
View File
@@ -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)