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:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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]");
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user