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.
378 lines
12 KiB
C
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,
|
|
};
|