Files
u-boot-krane/drivers/video/mt8183_scanout.c
T
vhaudiquet 055563bd92 krane: restore CONFIG_USB dropped by the R35 defconfig edit; rotate 270
The R35 rotation edit used PUT 77.=77 against a stale line number and
overwrote CONFIG_USB=y with a duplicate VIDEO block, leaving the USB
menuconfig unset: no USB controller, no usb command, no keyboard.
Restore CONFIG_USB=y (deduplicate VIDEO) and verify the full USB set
in .config after the defconfig re-run.

Also flip the console rotation from 1 (90 degrees clockwise) to 3
(270 degrees clockwise): rot=1 read upside down on the device with
the keyboard attached.
2026-08-30 20:46:36 +02:00

378 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* MT8183 display scanout driver for firmware-initialized pipelines.
*
* On the MT8183 kukui family of ChromeOS devices (e.g. the Lenovo IdeaPad
* Duet, "google,krane"), the display pipeline (MMSYS -> OVL0 -> OVL0_2L ->
* RDMA -> COLOR -> DSI -> panel) is fully initialized and running by the
* time control is passed to the next boot stage: coreboot (through
* libpayload/depthcharge) sets up the panel and programs the overlay scanout
* address. The payload, however, is handed over with the pipeline STOPPED:
* depthcharge's display_cleanup() disables the overlay engines and turns the
* backlight off right before jumping to the payload.
*
* This driver does not initialize any display hardware. It only revives the
* pipeline (re-enabling the overlay engines and the backlight), discovers
* the geometry and the live scanout address from the coreboot table
* framebuffer record (falling back to the OVL L0 layer address register,
* which still holds the address depthcharge used), and hands the surface to
* the video uclass so the standard vidconsole can render into it.
*/
#include <dm.h>
#include <video.h>
#include <video_font.h> /* Get font data, width and height */
#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.
*/
#define DISP_REG_OVL_L0_ADDR 0x0f40
#define DISP_REG_OVL_EN 0x000c
#define DISP_REG_OVL0_2L_EN 0x100c
/* TEMPORARY bring-up diagnostics (krane Round 24) — do not upstream:
* paint a band into the live depthcharge scanout. */
/* TEMPORARY bring-up diagnostics (krane Round 30) — do not upstream:
* minimal framebuffer text logger using the built-in 8x16 font, so
* abort dumps and probe logs are readable on the panel even before
* the console exists. */
static void krane_fb_log_char(const struct video_fontdata *f,
u32 base, u32 x0, u32 y0, char c, u32 color)
{
const u8 *glyph = &f->video_fontdata[(u8)c * f->height];
u32 r, b;
if (c < 32 || c > 126)
c = '?';
glyph = &f->video_fontdata[(u8)c * f->height];
for (r = 0; r < (u32)f->height; r++) {
u32 *row = (u32 *)(uintptr_t)(base +
(u64)(y0 + r) * 4800 + (u64)x0 * 4);
u8 bits = glyph[r];
for (b = 0; b < (u32)f->width; b++)
if (bits & (0x80 >> b))
row[b] = color;
}
}
static void krane_fb_log(const char *s)
{
const struct video_fontdata *f = &fonts[0];
static u32 line, col;
u32 base = readl((void __iomem *)0x14008f40);
if (base < 0x40000000)
return;
if (dcache_status())
mmu_map_region(base, 0x8ca000, false);
for (; *s; s++) {
if (*s == '\n' || col + f->width > 1200) {
line++;
col = 0;
if (*s == '\n')
continue;
}
krane_fb_log_char(f, base, col, 1700 + line * f->height,
*s, 0x00ffffff);
col += f->width;
}
if (dcache_status())
flush_dcache_range((uintptr_t)(base + 1690ULL * 4800),
(uintptr_t)(base + 1920ULL * 4800));
}
static void krane_fb_log_hex(const char *prefix, unsigned long v)
{
char buf[32], *p = buf;
int i;
const char hexdigits[] = "0123456789abcdef";
for (i = 0; prefix[i] && i < 15; i++)
*p++ = prefix[i];
*p++ = '0';
*p++ = 'x';
for (i = (sizeof(v) * 8) - 4; i >= 0; i -= 4)
*p++ = hexdigits[(v >> i) & 0xf];
*p = 0;
krane_fb_log(buf);
krane_fb_log("\n");
}
/* Called from the arm64 abort handlers (temporary wiring in
* arch/arm/lib/interrupts_64.c): draw ESR/ELR/FAR on the panel. */
void krane_fb_log_abort(unsigned long esr, unsigned long elr)
{
unsigned long far, cvel;
asm volatile("mrs %0, CurrentEL" : "=r"(cvel));
switch (cvel >> 2) {
case 1:
asm volatile("mrs %0, far_el1" : "=r"(far));
break;
case 3:
asm volatile("mrs %0, far_el3" : "=r"(far));
break;
default:
asm volatile("mrs %0, far_el2" : "=r"(far));
}
krane_fb_log("\nABORT!\n");
krane_fb_log_hex("ESR=", esr);
krane_fb_log_hex("ELR=", elr);
krane_fb_log_hex("FAR=", far);
}
/*
* 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:
*
* TEMPORARY bring-up diagnostics (krane Round 24) — do not upstream:
* paint a band into the live depthcharge scanout. */
static void krane_diag_band(u32 y0, u32 rows, u32 color)
{
u32 base = readl((void __iomem *)0x14008f40);
u32 *p, n = rows * 1200, i;
if (base < 0x40000000)
return;
if (dcache_status())
mmu_map_region(base, 0x8ca000, false);
p = (u32 *)(uintptr_t)(base + (u64)y0 * 4800);
for (i = 0; i < n; i++)
p[i] = color;
if (dcache_status())
flush_dcache_range((uintptr_t)p, (uintptr_t)(p + n));
}
/*
* DISP_PWM (pin 43) and EN_LCD_BL (PERIPHERAL_EN13, pin 176); depthcharge's
* kukui_backlight_update() drives both high to turn the backlight on.
*/
#define MTK_GPIO_BASE 0x10005000
#define MTK_GPIO_DOUT_SET(pin) (MTK_GPIO_BASE + 0x100 + ((pin) / 32) * 16 + 4)
#define MTK_GPIO_DOUT_BIT(pin) BIT((pin) % 32)
#define PAD_DISP_PWM 43
#define PAD_EN_LCD_BL 176
/*
* The coreboot table sits at a fixed address on kukui: 0xffed9000, size
* 0x380 (coreboot memlayout; confirmed on the device through
* /sys/firmware/fdt and the coreboot sysfs tags). It is deliberately
* hardcoded here rather than read from the handoff DTB: on this platform
* the FDT pointer passed at entry cannot be relied upon, and U-Boot boots
* with its own embedded control DTB which has no /firmware/coreboot node.
*/
#define COREBOOT_TABLE_ADDR 0xffed9000
#define LB_TAG_FRAMEBUFFER 0x12
/*
* Layout from coreboot src/commonlib/include/commonlib/coreboot_tables.h:
* struct lb_header: sig[4] "LBIO", header_bytes, header_checksum,
* table_bytes, table_checksum, table_entries (LE u32)
* struct lb_record: tag, size
* struct lb_framebuffer (record payload):
* physical_address @8 (4-byte-aligned LE u64), x_resolution @16,
* y_resolution @20, bytes_per_line @24, bits_per_pixel @28,
* red_pos/size @29/30, green @31/32, blue @33/34,
* reserved @35/36, orientation @37, flags @38, pad @39; size 40.
*
* physical_address == 0 is legitimate: upstream coreboot 4.14 (the
* generation shipped on kukui) publishes the framebuffer record with
* fb_addr=0. The live scanout address then comes from OVL_L0_ADDR.
*/
struct lb_framebuffer {
u32 tag;
u32 size;
u64 physical_address;
u32 x_resolution;
u32 y_resolution;
u32 bytes_per_line;
u8 bits_per_pixel;
u8 red_pos;
u8 red_size;
u8 green_pos;
u8 green_size;
u8 blue_pos;
u8 blue_size;
u8 reserved_pos;
u8 reserved_size;
};
static int find_framebuffer(u64 table, struct lb_framebuffer *fb)
{
void *base = (void *)(uintptr_t)table;
u32 header_bytes, entries, i;
void *rec;
if (get_unaligned_le32(base) != 0x4f49424c) /* "LBIO" */
return -ENOENT;
header_bytes = get_unaligned_le32(base + 4);
entries = get_unaligned_le32(base + 20);
if (header_bytes < 24 || header_bytes > 4096 ||
entries == 0 || entries > 4096)
return -EINVAL;
rec = base + header_bytes;
for (i = 0; i < entries; i++) {
u32 tag = get_unaligned_le32(rec);
u32 rsize = get_unaligned_le32(rec + 4);
if (rsize < 8)
return -EINVAL;
if (tag == LB_TAG_FRAMEBUFFER) {
if (rsize < sizeof(*fb))
return -EINVAL;
memcpy(fb, rec, sizeof(*fb));
fb->physical_address =
get_unaligned_le64(rec + 8);
return 0;
}
rec += rsize;
}
return -ENOENT;
}
static int mt8183_scanout_probe(struct udevice *dev)
{
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
struct lb_framebuffer fb;
fdt_addr_t ovl;
u64 addr;
int ret;
krane_diag_band(500, 40, 0x00ff00ff); /* magenta: probe entered */
krane_fb_log("PROBE");
ovl = dev_read_addr(dev);
if (ovl == FDT_ADDR_T_NONE) {
krane_diag_band(780, 40, 0x00ff0000); /* red: ovl read failed */
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));
krane_diag_band(520, 20, 0x0000ff80); /* mint: revival writes done */
krane_fb_log(" REVIVE");
/*
* 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);
krane_diag_band(540, 20, 0x008000ff); /* violet: table mapped */
krane_fb_log(" MAPTBL");
ret = find_framebuffer(COREBOOT_TABLE_ADDR, &fb);
if (ret) {
krane_diag_band(800, 40, 0x00ff0000); /* red: lbio failed */
return log_msg_ret("lbio", ret);
}
krane_diag_band(560, 20, 0x00ffff80); /* pale yellow: LBIO found */
krane_fb_log(" LBIO");
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) {
krane_diag_band(820, 40, 0x00ff0000); /* red: fmt mismatch */
return log_msg_ret("fmt", -ENOTSUPP);
}
krane_diag_band(580, 20, 0x0080ffff); /* pale cyan: fmt OK */
krane_fb_log(" FMT");
/*
* 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) {
krane_diag_band(840, 40, 0x00ff0000); /* red: no scanout addr */
return log_msg_ret("scanout", -ENODEV);
}
krane_diag_band(620, 40, 0x00aaaaaa); /* gray: geometry resolved */
krane_fb_log(" GEO");
plat->base = addr;
plat->size = fb.bytes_per_line * fb.y_resolution;
/* The scanout surface is above the DTB DRAM window: map it. */
mmu_map_region(plat->base, ALIGN(plat->size, SZ_4K), false);
video_set_flush_dcache(dev, true);
uc_priv->bpix = VIDEO_BPP32;
uc_priv->xsize = fb.x_resolution;
uc_priv->ysize = fb.y_resolution;
uc_priv->line_length = fb.bytes_per_line;
/*
* 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;
printf("Video: MT8183 scanout %dx%d@32bpp at %llx\n",
uc_priv->xsize, uc_priv->ysize, (unsigned long long)plat->base);
return 0;
}
static const struct udevice_id mt8183_scanout_ids[] = {
{ .compatible = "mediatek,mt8183-disp-ovl" },
{ }
};
U_BOOT_DRIVER(mt8183_scanout) = {
.name = "mt8183_scanout",
.id = UCLASS_VIDEO,
.of_match = mt8183_scanout_ids,
.probe = mt8183_scanout_probe,
};