Author SHA1 Message Date
Vincent Haudiquet 3fd015cd23 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.
2026-09-02 00:25:07 +02:00
Vincent Haudiquet 3bb613ee2f video: add MediaTek MT8183 MIPI DSI host driver
Add a driver for the MT8183 MIPI DSI host controller (DSI0). It
implements the DSI_HOST uclass: init() brings up the clock gates, the
external MIPI TX D-PHY (through the generic PHY API), the D-PHY
timing registers and the video mode timing, and enable() switches the
host to video mode and starts the stream, so that panel
initialization can be performed over the command FIFO in between.

The register map and the D-PHY timing computation are ported from the
Linux kernel driver drivers/gpu/drm/mediatek/mtk_dsi.c; the video mode
timing computation (including the D-PHY turnaround adjustment taken
out of the horizontal porch) and the command queue programming follow
the ChromeOS coreboot port src/soc/mediatek/common/dsi.c, which is
proven to drive this controller on the kukui family of boards.

DSI transfers are performed through the command queue in low power
mode for panels that request it and high speed otherwise, with bus
turn-around and receive support for read commands. The panel child
node of the DSI controller is bound from the bind() hook.

Signed-off-by: Vincent Haudiquet <vhaudiquet@gmail.com>
2026-09-01 18:27:53 +02:00
Vincent Haudiquet 897cbb89c5 phy: add MediaTek MT8183 MIPI TX D-PHY driver
Add a PHY driver for the MT8183 MIPI TX block, the analog MIPI D-PHY
used by the MT8183 MIPI DSI host. The PLL programming and the analog
lane bring-up sequence are ported from the Linux kernel driver
drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c.

The driver implements the generic PHY API: the DSI host selects the
data rate with generic_phy_set_mode(PHY_MODE_MIPI_DPHY, <rate in
bit/s>) and then powers the PHY up with generic_phy_init() and
generic_phy_power_on(). The efuse lane calibration of the Linux driver
is not implemented; uncalibrated lanes are what the ChromeOS coreboot
port programs on the same hardware (src/soc/mediatek/common/
mtk_mipi_dphy.c in coreboot 4.14), so calibration is treated as an
optional refinement.

Signed-off-by: Vincent Haudiquet <vhaudiquet@gmail.com>
2026-09-01 18:27:11 +02:00
Vincent Haudiquet 4e0541e207 gpio: add MediaTek MT8183 GPIO driver
Add a GPIO driver for the MT8183 GPIO controller at 0x10005000,
covering the 192 pins in six 32-pin groups. Each group owns a 16-byte
window (value at +0, set at +4, reset at +8) for the DIR, DOUT and DIN
registers, so output values and direction changes are performed
atomically by writing the target bit to the set or reset register.
The register layout was ported from the ChromeOS depthcharge driver
src/drivers/gpio/mt8183.h (GpioRegs/GpioValRegs).

Only the plain GPIO function is implemented. The MT8183 multiplexes
pads between several functions through per-pin mode registers which
this driver does not touch; on the kukui family of boards every pad
used as a GPIO at boot time (panel reset, panel power enables, the
two display backlight controls) is already muxed to GPIO mode by the
boot firmware before U-Boot runs.

This is needed so that display drivers can drive the panel reset and
panel power enable pads described in the device tree.

Signed-off-by: Vincent Haudiquet <vhaudiquet@gmail.com>
2026-09-01 18:11:57 +02:00
Vincent Haudiquet fab1110ff3 clk: mediatek: mt8183: add MMSYS display clock gates
The MT8183 clock driver only models the topckgen, apmixedsys and
infracfg clock controllers; the MMSYS display gates (OVL0, OVL0_2L,
RDMA0, COLOR0, DSI0 engine/interface and friends) had no provider, so
display drivers could not enable the clocks described in the device
tree 'mmsys' node (mediatek,mt8183-mmsys with #clock-cells = <1>).

Add the two gate register groups (CG_CON0 at 0x100 and CG_CON1 at
0x110, with set/clr registers at +4/+8) and the complete gate list
ported from the Linux kernel driver drivers/clk/mediatek/
clk-mt8183-mm.c. All gates are children of the MM or DPI0 topckgen
muxes (or the 26M fixed clock).

This is a prerequisite for bringing the MT8183 display pipeline up
from scratch instead of relying on the boot firmware having left the
display clocks enabled.

Signed-off-by: Vincent Haudiquet <vhaudiquet@gmail.com>
2026-09-01 00:22:49 +02:00
vhaudiquet 1ae771d9f9 video: mt8183_scanout: reserve the scanout buffer in FDT and LMB
The scanout buffer used by this driver is the surface allocated by the
boot firmware (coreboot/depthcharge) and handed over live. It is not
tracked by U-Boot's memory management, so it shows up as conventional
RAM: once the kernel's page allocator goes live, it reuses the region
and the display corrupts with moving garbage bands.

Record a memory reservation for the scanout buffer in the control FDT
and feed it to LMB. EFI page allocations (e.g. the initrd loaded by a
bootloader) are LMB-gated, the EFI DT fixup marks the range reserved in
the memory map, and the kernel honors the /memreserve of the received
DTB.

Signed-off-by: vhaudiquet <vhaudiquet@localhost>
2026-08-31 23:45:37 +02:00
vhaudiquet c78ba55fd4 video: console: fix scrolling on rotated consoles
For odd rotations (rot=1 and rot=3) the console rows advance along the
physical X axis by the font height (priv->y_charsize): console_move_rows()
and console_set_row() treat a row as fontdata->height physical columns.

But the scroll condition in vidconsole_newline() compared the cursor
position against the font *width* (priv->x_charsize):

    priv->ycur + priv->x_charsize > vid_priv->xsize

The condition is only met at the bottom boundary when
(xsize % font_height) < font_width. With CONFIG_VIDEO_FONT_16X32 on a
1200x1920 panel used with rot=3, xsize % 32 == 16 == font_width, so the
condition is never true at the boundary: the console fills the visible
rows and then keeps drawing past the right screen edge, corrupting the
following scanout line (and past the framebuffer allocation on the last
scanout line), with no visible scrolling.

Use priv->y_charsize in the odd-rotation branch, matching the even-
rotation branch which already correctly compares against the character
height. With this the cursor wraps back to row 0 and scrolling advances
by one row per newline as expected.

Signed-off-by: vhaudiquet <vhaudiquet@localhost>
2026-08-31 12:51:29 +02:00
vhaudiquetandvhaudiquet ac15f4dcbc krane: rotate the console 270 degrees clockwise for landscape use
The BOE TV101WUM-NL6 panel is mounted rotated 270 degrees in the
krane chassis (rotation property on the panel node in the upstream
DT). With the keyboard attached the tablet is used in landscape, so
set video rot=1 and enable CONFIG_CONSOLE_ROTATION: the uclass then
binds the rotated vidconsole1 driver, which writes glyphs rotated by
90 degrees clockwise into the native 1200x1920 portrait scanout and
uses the swapped console geometry (240 columns x 75 rows).

The framebuffer geometry and the depthcharge scanout path are
unchanged; only the console text placement rotates.
2026-08-31 00:19:46 +02:00
vhaudiquetandvhaudiquet 6f79f12858 krane: bind the mtu3 SSUSB container so its xhci child is scanned
DM scans only the immediate subnodes of a bound node; recursion into
children happens when a bound bus driver runs dm_scan_fdt_dev. Nothing
in U-Boot binds the "mediatek,mtu3" ssusb@11201000 node, so its xhci
child (usb@11200000) was never bound, no UCLASS_USB device existed and
usb start() reported "No USB controllers found".

Add "simple-mfd" to the ssusb compatible in the -u-boot.dtsi overlay
so the generic simple-bus driver binds the node and scans its children.
2026-08-31 00:19:46 +02:00
vhaudiquetandvhaudiquet ae757417c8 krane: enable USB xHCI, T-PHY and USB keyboard for the pogo keyboard
The pogo keyboard is a USB HID device behind the GL610 hub on
usb@11200000. Enable the USB stack (USB, DM_USB, USB_XHCI_HCD,
USB_XHCI_MTK, USB_STORAGE, CMD_USB), the MTK T-PHY (PHY_MTK_TPHY),
USB_KEYBOARD and CONFIG_USE_PREBOOT (preboot defaults to "usb start"
with USB_KEYBOARD) so the bus is enumerated before the prompt.

The upstream mt8183.dtsi models the SSUSB block for the Linux mtu3
dual-role driver: the IPPC register space and the phys live on the
parent mtu3 node while the xhci child has only its "mac" register.
U-Boot binds the "mediatek,mtk-xhci" child directly and expects
mac+ippc and a phys list on that node, so add a board
-u-boot.dtsi overlay (auto-included after the upstream dts) that
supplies them. No upstream DT files are modified.

stdin=serial,usbkbd routes the prompt through the USB keyboard once
the usb_kbd stdio device is registered.
2026-08-31 00:19:44 +02:00
vhaudiquetandvhaudiquet 2830892520 krane: override get_page_table_size for dynamic scanout mappings 2026-08-30 23:33:12 +02:00
vhaudiquetandvhaudiquet ed1d014b7b krane: default env routes stdout through vidconsole; env nowhere for payload stage 2026-08-30 23:28:40 +02:00
vhaudiquetandvhaudiquet f7faca4f11 krane: CONFIG_TEXT_BASE=0x4C001000 (8-aligned, keeps _start at image base) 2026-08-30 23:28:38 +02:00
24 changed files with 2773 additions and 2164 deletions
+7 -1984
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
* U-Boot-only additions for the MT8183 Kukui (krane) boards.
*
* The upstream mt8183.dtsi models the SSUSB block for the Linux mtu3
* dual-role driver: the IPPC register space and the phys are declared
* on the parent "mediatek,mtu3" node (ssusb@11201000), and the host
* child (usb@11200000) carries only its "mac" register.
*
* U-Boot has no mtu3 driver; it binds the "mediatek,mtk-xhci" child
* directly with drivers/usb/host/xhci-mtk.c, which expects the "mac"
* and "ippc" register names and a phys list on the xHCI node itself.
* Add the missing ippc address (taken from the ssusb node's "ippc"
* reg entry) and the port phys there.
*
* DM only scans the subnodes of nodes that a driver binds (bus drivers
* recurse via dm_scan_fdt_dev). Nothing binds "mediatek,mtu3", so the
* xHCI child would never be bound and usb_start() would report "No USB
* controllers found". Adding "simple-mfd" makes the generic simple-bus
* driver bind the ssusb node and scan its children.
*/
&ssusb {
compatible = "mediatek,mt8183-mtu3", "mediatek,mtu3", "simple-mfd";
};
&usb_host {
reg = <0x0 0x11200000 0x0 0x1000>,
<0x0 0x11203e00 0x0 0x0100>;
reg-names = "mac", "ippc";
phys = <&u2port0 PHY_TYPE_USB2>,
<&u3port0 PHY_TYPE_USB3>;
};
+3
View File
@@ -0,0 +1,3 @@
stdin=serial,usbkbd
stdout=serial,vidconsole
stderr=serial,vidconsole
+11 -1
View File
@@ -3,10 +3,20 @@
* Copyright (C) 2020 BayLibre SAS
* Author: Fabien Parent <fparent@baylibre.com>
*/
#include <dm.h>
#include <net.h>
/*
* The krane scanout driver maps the coreboot table and the live
* framebuffer dynamically after relocation (mmu_map_region), which the
* default page-table budget (sized for the static memory map only)
* does not account for. Reserve enough for those mappings.
*/
u64 get_page_table_size(void)
{
return 0x40000;
}
int board_init(void)
{
struct udevice *dev;
+19 -6
View File
@@ -2,7 +2,7 @@ CONFIG_ARM=y
CONFIG_COUNTER_FREQUENCY=13000000
CONFIG_POSITION_INDEPENDENT=y
CONFIG_ARCH_MEDIATEK=y
CONFIG_TEXT_BASE=0x4c000000
CONFIG_TEXT_BASE=0x4C001000
CONFIG_SYS_MALLOC_F_LEN=0x4000
CONFIG_NR_DRAM_BANKS=1
CONFIG_ENV_SIZE=0x1000
@@ -55,18 +55,31 @@ CONFIG_CMD_SYSBOOT=y
CONFIG_CMD_EXT4=y
CONFIG_CMD_FAT=y
CONFIG_CMD_FS_GENERIC=y
CONFIG_CMD_USB=y
# CONFIG_DOS_PARTITION is not set
CONFIG_ENV_IS_IN_MMC=y
CONFIG_ENV_MMC_EMMC_HW_PARTITION=2
CONFIG_ENV_IS_NOWHERE=y
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
# CONFIG_POWER is not set
CONFIG_BAUDRATE=921600
CONFIG_PHY=y
CONFIG_PHY_MTK_TPHY=y
CONFIG_CONSOLE_ROTATION=y
CONFIG_USB=y
CONFIG_DM_USB=y
CONFIG_USB_XHCI_HCD=y
CONFIG_USB_XHCI_MTK=y
CONFIG_USB_KEYBOARD=y
CONFIG_USB_STORAGE=y
CONFIG_BAUDRATE=115200
CONFIG_DM_SERIAL=y
CONFIG_DEBUG_UART_ANNOUNCE=y
CONFIG_MTK_SERIAL=y
+138
View File
@@ -787,13 +787,133 @@ 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),
};
static const struct mtk_gate_regs mm0_cg_regs = {
.set_ofs = 0x104,
.clr_ofs = 0x108,
.sta_ofs = 0x100,
};
static const struct mtk_gate_regs mm1_cg_regs = {
.set_ofs = 0x114,
.clr_ofs = 0x118,
.sta_ofs = 0x110,
};
#define GATE_MM0(_id, _parent, _shift) { \
.id = _id, \
.parent = _parent, \
.regs = &mm0_cg_regs, \
.shift = _shift, \
.flags = CLK_GATE_SETCLR | CLK_PARENT_TOPCKGEN, \
}
#define GATE_MM1(_id, _parent, _shift) { \
.id = _id, \
.parent = _parent, \
.regs = &mm1_cg_regs, \
.shift = _shift, \
.flags = CLK_GATE_SETCLR | CLK_PARENT_TOPCKGEN, \
}
/*
* MMSYS display gates. Gate bits and their grouping into CG_CON0/CG_CON1
* match the Linux kernel driver drivers/clk/mediatek/clk-mt8183-mm.c.
*/
static const struct mtk_gate mm_clks[] = {
/* MM0 */
GATE_MM0(CLK_MM_SMI_COMMON, CLK_TOP_MUX_MM, 0),
GATE_MM0(CLK_MM_SMI_LARB0, CLK_TOP_MUX_MM, 1),
GATE_MM0(CLK_MM_SMI_LARB1, CLK_TOP_MUX_MM, 2),
GATE_MM0(CLK_MM_GALS_COMM0, CLK_TOP_MUX_MM, 3),
GATE_MM0(CLK_MM_GALS_COMM1, CLK_TOP_MUX_MM, 4),
GATE_MM0(CLK_MM_GALS_CCU2MM, CLK_TOP_MUX_MM, 5),
GATE_MM0(CLK_MM_GALS_IPU12MM, CLK_TOP_MUX_MM, 6),
GATE_MM0(CLK_MM_GALS_IMG2MM, CLK_TOP_MUX_MM, 7),
GATE_MM0(CLK_MM_GALS_CAM2MM, CLK_TOP_MUX_MM, 8),
GATE_MM0(CLK_MM_GALS_IPU2MM, CLK_TOP_MUX_MM, 9),
GATE_MM0(CLK_MM_MDP_DL_TXCK, CLK_TOP_MUX_MM, 10),
GATE_MM0(CLK_MM_IPU_DL_TXCK, CLK_TOP_MUX_MM, 11),
GATE_MM0(CLK_MM_MDP_RDMA0, CLK_TOP_MUX_MM, 12),
GATE_MM0(CLK_MM_MDP_RDMA1, CLK_TOP_MUX_MM, 13),
GATE_MM0(CLK_MM_MDP_RSZ0, CLK_TOP_MUX_MM, 14),
GATE_MM0(CLK_MM_MDP_RSZ1, CLK_TOP_MUX_MM, 15),
GATE_MM0(CLK_MM_MDP_TDSHP, CLK_TOP_MUX_MM, 16),
GATE_MM0(CLK_MM_MDP_WROT0, CLK_TOP_MUX_MM, 17),
GATE_MM0(CLK_MM_MDP_WDMA0, CLK_TOP_MUX_MM, 18),
GATE_MM0(CLK_MM_FAKE_ENG, CLK_TOP_MUX_MM, 19),
GATE_MM0(CLK_MM_DISP_OVL0, CLK_TOP_MUX_MM, 20),
GATE_MM0(CLK_MM_DISP_OVL0_2L, CLK_TOP_MUX_MM, 21),
GATE_MM0(CLK_MM_DISP_OVL1_2L, CLK_TOP_MUX_MM, 22),
GATE_MM0(CLK_MM_DISP_RDMA0, CLK_TOP_MUX_MM, 23),
GATE_MM0(CLK_MM_DISP_RDMA1, CLK_TOP_MUX_MM, 24),
GATE_MM0(CLK_MM_DISP_WDMA0, CLK_TOP_MUX_MM, 25),
GATE_MM0(CLK_MM_DISP_COLOR0, CLK_TOP_MUX_MM, 26),
GATE_MM0(CLK_MM_DISP_CCORR0, CLK_TOP_MUX_MM, 27),
GATE_MM0(CLK_MM_DISP_AAL0, CLK_TOP_MUX_MM, 28),
GATE_MM0(CLK_MM_DISP_GAMMA0, CLK_TOP_MUX_MM, 29),
GATE_MM0(CLK_MM_DISP_DITHER0, CLK_TOP_MUX_MM, 30),
GATE_MM0(CLK_MM_DISP_SPLIT, CLK_TOP_MUX_MM, 31),
/* MM1 */
GATE_MM1(CLK_MM_DSI0_MM, CLK_TOP_MUX_MM, 0),
GATE_MM1(CLK_MM_DSI0_IF, CLK_TOP_MUX_MM, 1),
GATE_MM1(CLK_MM_DPI_MM, CLK_TOP_MUX_MM, 2),
GATE_MM1(CLK_MM_DPI_IF, CLK_TOP_MUX_DPI0, 3),
GATE_MM1(CLK_MM_FAKE_ENG2, CLK_TOP_MUX_MM, 4),
GATE_MM1(CLK_MM_MDP_DL_RX, CLK_TOP_MUX_MM, 5),
GATE_MM1(CLK_MM_IPU_DL_RX, CLK_TOP_MUX_MM, 6),
GATE_MM1(CLK_MM_26M, CLK_TOP_F26M_CK_D2, 7),
GATE_MM1(CLK_MM_MMSYS_R2Y, CLK_TOP_MUX_MM, 8),
GATE_MM1(CLK_MM_DISP_RSZ, CLK_TOP_MUX_MM, 9),
GATE_MM1(CLK_MM_MDP_AAL, CLK_TOP_MUX_MM, 10),
GATE_MM1(CLK_MM_MDP_CCORR, CLK_TOP_MUX_MM, 11),
GATE_MM1(CLK_MM_DBI_MM, CLK_TOP_MUX_MM, 12),
GATE_MM1(CLK_MM_DBI_IF, CLK_TOP_MUX_DPI0, 13),
};
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),
};
static const struct udevice_id mt8183_apmixed_compat[] = {
{
.compatible = "mediatek,mt8183-apmixedsys",
@@ -847,3 +967,21 @@ U_BOOT_DRIVER(mt8183_clk_infracfg) = {
.ops = &mtk_clk_topckgen_ops,
.flags = DM_FLAG_PRE_RELOC,
};
static const struct udevice_id mt8183_mmsys_compat[] = {
{
.compatible = "mediatek,mt8183-mmsys",
.data = (ulong)&mt8183_mmsys_tree,
},
{ }
};
U_BOOT_DRIVER(mt8183_clk_mmsys) = {
.name = "mt8183-mmsys",
.id = UCLASS_CLK,
.of_match = mt8183_mmsys_compat,
.probe = mtk_clk_probe,
.priv_auto = sizeof(struct mtk_clk_priv),
.ops = &mtk_clk_topckgen_ops,
.flags = DM_FLAG_PRE_RELOC,
};
+8
View File
@@ -772,3 +772,11 @@ config MPFS_GPIO
Enable to support the GPIO driver on Polarfire SoC
endif
config MT8183_GPIO
bool "MediaTek MT8183 GPIO driver"
depends on DM_GPIO && ARCH_MEDIATEK
help
Say yes here to support the MediaTek MT8183 GPIO controller. Only
the plain GPIO function is provided: pin mode multiplexing is the
responsibility of the boot firmware.
+1
View File
@@ -83,3 +83,4 @@ obj-$(CONFIG_GPIO_SCMI) += gpio_scmi.o
obj-$(CONFIG_$(PHASE_)ADP5585_GPIO) += adp5585_gpio.o
obj-$(CONFIG_RZG2L_GPIO) += rzg2l-gpio.o
obj-$(CONFIG_MPFS_GPIO) += mpfs_gpio.o
obj-$(CONFIG_MT8183_GPIO) += mt8183_gpio.o
+154
View File
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek MT8183 GPIO driver.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* Ported from the register layout of the ChromeOS depthcharge driver
* src/drivers/gpio/mt8183.h (GpioRegs/GpioValRegs), which is the same
* hardware the Linux pinctrl driver drivers/pinctrl/mediatek/
* pinctrl-mtk-mt8183.c drives through its common code.
*
* Only the plain GPIO function is supported: pin direction (DIR), output
* value (DOUT) and input value (DIN). The MT8183 multiplexes most pads
* between several functions through per-pin mode registers which this
* driver does not touch; the boot firmware is expected to have put the
* requested pads into GPIO mode already, which holds for every pad this
* platform uses as a GPIO at boot time.
*
* Each of the 192 pins lives in one of six 32-pin groups. Every group
* owns a 16-byte GpioValRegs window (value at +0, set at +4, reset at
* +8) for each of dir/dout/din, so bits are set or cleared atomically
* by writing the target bit to the set/reset register.
*/
#include <dm.h>
#include <asm/io.h>
#include <asm/gpio.h>
#include <linux/bitops.h>
#include <linux/sizes.h>
#define GPIO_VAL_BITS 32
#define GPIO_NUM_GROUPS 6
#define GPIO_VAL_REGS_SIZE 16
/* Offsets of the per-group GpioValRegs blocks within the controller. */
#define GPIO_DIR_OFFSET 0x000
#define GPIO_DOUT_OFFSET 0x100
#define GPIO_DIN_OFFSET 0x200
struct mt8183_gpio_priv {
void __iomem *base;
};
static void __iomem *mt8183_gpio_val_reg(struct mt8183_gpio_priv *priv,
u32 offset, unsigned int pin, int val)
{
unsigned int group = pin / GPIO_VAL_BITS;
return priv->base + offset + group * GPIO_VAL_REGS_SIZE + val * 4;
}
static int mt8183_gpio_direction_input(struct udevice *dev, unsigned int pin)
{
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
if (pin >= GPIO_VAL_BITS * GPIO_NUM_GROUPS)
return -EINVAL;
/* dir.reset clears the bit: the pad becomes an input. */
writel(BIT(pin % GPIO_VAL_BITS),
mt8183_gpio_val_reg(priv, GPIO_DIR_OFFSET, pin, 2));
return 0;
}
static int mt8183_gpio_direction_output(struct udevice *dev, unsigned int pin,
int value)
{
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
if (pin >= GPIO_VAL_BITS * GPIO_NUM_GROUPS)
return -EINVAL;
/* dir.set sets the bit: the pad becomes an output. */
writel(BIT(pin % GPIO_VAL_BITS),
mt8183_gpio_val_reg(priv, GPIO_DIR_OFFSET, pin, 1));
writel(BIT(pin % GPIO_VAL_BITS),
mt8183_gpio_val_reg(priv, GPIO_DOUT_OFFSET, pin,
value ? 1 : 2));
return 0;
}
static int mt8183_gpio_get_value(struct udevice *dev, unsigned int pin)
{
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
return !!(readl(mt8183_gpio_val_reg(priv, GPIO_DIN_OFFSET, pin, 0)) &
BIT(pin % GPIO_VAL_BITS));
}
static int mt8183_gpio_set_value(struct udevice *dev, unsigned int pin,
int value)
{
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
/* dout.set (val = 1) or dout.reset (val = 2) drives the pin. */
writel(BIT(pin % GPIO_VAL_BITS),
mt8183_gpio_val_reg(priv, GPIO_DOUT_OFFSET, pin,
value ? 1 : 2));
return 0;
}
static int mt8183_gpio_get_function(struct udevice *dev, unsigned int pin)
{
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
if (pin >= GPIO_VAL_BITS * GPIO_NUM_GROUPS)
return GPIOF_UNUSED;
if (readl(mt8183_gpio_val_reg(priv, GPIO_DIR_OFFSET, pin, 0)) &
BIT(pin % GPIO_VAL_BITS))
return GPIOF_OUTPUT;
return GPIOF_INPUT;
}
static const struct dm_gpio_ops mt8183_gpio_ops = {
.direction_input = mt8183_gpio_direction_input,
.direction_output = mt8183_gpio_direction_output,
.get_value = mt8183_gpio_get_value,
.set_value = mt8183_gpio_set_value,
.get_function = mt8183_gpio_get_function,
};
static int mt8183_gpio_probe(struct udevice *dev)
{
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
priv->base = dev_read_addr_ptr(dev);
if (!priv->base)
return -EINVAL;
uc_priv->gpio_count = GPIO_VAL_BITS * GPIO_NUM_GROUPS;
uc_priv->bank_name = "pio";
return 0;
}
static const struct udevice_id mt8183_gpio_ids[] = {
{ .compatible = "mediatek,mt8183-pinctrl" },
{ }
};
U_BOOT_DRIVER(mt8183_gpio) = {
.name = "mt8183_gpio",
.id = UCLASS_GPIO,
.of_match = mt8183_gpio_ids,
.probe = mt8183_gpio_probe,
.ops = &mt8183_gpio_ops,
.priv_auto = sizeof(struct mt8183_gpio_priv),
};
+7
View File
@@ -359,3 +359,10 @@ config PHY_COMMON_PROPS
device tree node.
endmenu
config PHY_MTK_MIPI_TX
bool "MediaTek MT8183 MIPI TX D-PHY driver"
depends on PHY && ARCH_MEDIATEK
help
Enable this to support the MediaTek MT8183 MIPI TX (MIPI D-PHY)
used by the MT8183 MIPI DSI host.
+1
View File
@@ -49,3 +49,4 @@ obj-y += ti/
obj-y += qcom/
obj-y += renesas/
obj-y += starfive/
obj-$(CONFIG_PHY_MTK_MIPI_TX) += phy-mtk-mipi-tx.o
+225
View File
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek MT8183 MIPI TX (MIPI D-PHY) driver.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* Ported from the Linux kernel driver drivers/phy/mediatek/
* phy-mtk-mipi-dsi-mt8183.c (PLL programming and analog lane
* configuration). The efuse calibration path of the Linux driver is not
* implemented; uncalibrated lanes work (this is what the ChromeOS
* coreboot port does on the same hardware, src/soc/mediatek/common/
* mtk_mipi_dphy.c in coreboot 4.14).
*/
#include <clk.h>
#include <dm.h>
#include <generic-phy.h>
#include <asm/io.h>
#include <div64.h>
#include <dm/device_compat.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/err.h>
/* Register offsets (MT8183 variant). */
#define MIPITX_LANE_CON 0x000c
#define RG_DSI_BG_LPF_EN BIT(6)
#define RG_DSI_BG_CORE_EN BIT(7)
#define RG_DSI_PAD_TIEL_SEL BIT(8)
#define MIPITX_VOLTAGE_SEL 0x0010
#define RG_DSI_HSTX_LDO_REF_SEL GENMASK(9, 6)
#define MIPITX_PLL_PWR 0x0028
#define MIPITX_PLL_CON0 0x002c
#define MIPITX_PLL_CON1 0x0030
#define MIPITX_PLL_CON4 0x003c
#define RG_DSI_PLL_IBIAS GENMASK(11, 10)
#define MIPITX_D2_SW_CTL_EN 0x0144
#define MIPITX_D0_SW_CTL_EN 0x0244
#define MIPITX_CK_CKMODE_EN 0x0328
#define DSI_CK_CKMODE_EN BIT(0)
#define MIPITX_CK_SW_CTL_EN 0x0344
#define MIPITX_D1_SW_CTL_EN 0x0444
#define MIPITX_D3_SW_CTL_EN 0x0544
#define DSI_SW_CTL_EN BIT(0)
#define AD_DSI_PLL_SDM_PWR_ON BIT(0)
#define AD_DSI_PLL_SDM_ISO_EN BIT(1)
#define RG_DSI_PLL_EN BIT(4)
#define RG_DSI_PLL_POSDIV GENMASK(10, 8)
#define MIPITX_RATE_MIN 125000000
#define MIPITX_RATE_MAX 1600000000
#define MIPITX_REF_CLOCK 26000000
struct mtk_mipi_tx {
void __iomem *regs;
u32 data_rate;
};
static int mtk_mipi_tx_pll_enable(struct mtk_mipi_tx *mipi_tx)
{
void __iomem *base = mipi_tx->regs;
unsigned int txdiv, txdiv0;
u64 pcw;
if (mipi_tx->data_rate >= 2000000000) {
txdiv = 1;
txdiv0 = 0;
} else if (mipi_tx->data_rate >= 1000000000) {
txdiv = 2;
txdiv0 = 1;
} else if (mipi_tx->data_rate >= 500000000) {
txdiv = 4;
txdiv0 = 2;
} else if (mipi_tx->data_rate > 250000000) {
txdiv = 8;
txdiv0 = 3;
} else if (mipi_tx->data_rate >= MIPITX_RATE_MIN) {
txdiv = 16;
txdiv0 = 4;
} else {
return -EINVAL;
}
/*
* PLL programming, from mtk_mipi_tx_pll_enable() in the Linux
* driver. The PLL input is the fixed 26 MHz clock.
*/
clrsetbits_le32(base + MIPITX_PLL_CON4, RG_DSI_PLL_IBIAS, 0);
setbits_le32(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON);
clrbits_le32(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
udelay(1);
clrbits_le32(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN);
pcw = ((u64)mipi_tx->data_rate * txdiv) << 24;
do_div(pcw, MIPITX_REF_CLOCK);
writel(pcw, base + MIPITX_PLL_CON0);
clrsetbits_le32(base + MIPITX_PLL_CON1, RG_DSI_PLL_POSDIV,
txdiv0 << 8);
setbits_le32(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
return 0;
}
static int mtk_mipi_tx_init(struct phy *phy)
{
struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
if (!mipi_tx->data_rate) {
dev_err(phy->dev, "data rate not configured\n");
return -EINVAL;
}
return mtk_mipi_tx_pll_enable(mipi_tx);
}
static int mtk_mipi_tx_power_on(struct phy *phy)
{
struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
void __iomem *base = mipi_tx->regs;
/*
* Analog lane bring-up, from mtk_dsi_configure_mipi_tx() in the
* coreboot port (proven on this platform): the LANE_CON writes
* enable the bandgap and program the default lane impedance
* calibration (bits 16-29), which the Linux driver instead
* programs through the per-lane RTCODE registers and an HSTX LDO
* reference.
*/
writel(0x3fff0180, base + MIPITX_LANE_CON);
udelay(40);
writel(0x3fff00c0, base + MIPITX_LANE_CON);
/* Switch off each lane until the DSI host enables it. */
clrbits_le32(base + MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN);
clrbits_le32(base + MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN);
clrbits_le32(base + MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN);
clrbits_le32(base + MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN);
clrbits_le32(base + MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN);
setbits_le32(base + MIPITX_CK_CKMODE_EN, DSI_CK_CKMODE_EN);
return 0;
}
static int mtk_mipi_tx_power_off(struct phy *phy)
{
struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
void __iomem *base = mipi_tx->regs;
setbits_le32(base + MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN);
setbits_le32(base + MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN);
setbits_le32(base + MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN);
setbits_le32(base + MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN);
setbits_le32(base + MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN);
writel(RG_DSI_PAD_TIEL_SEL | RG_DSI_BG_CORE_EN, base + MIPITX_LANE_CON);
writel(RG_DSI_PAD_TIEL_SEL, base + MIPITX_LANE_CON);
clrbits_le32(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
setbits_le32(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN);
clrbits_le32(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON);
return 0;
}
static int mtk_mipi_tx_set_mode(struct phy *phy, enum phy_mode mode,
int submode)
{
struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
if (mode != PHY_MODE_MIPI_DPHY)
return -EINVAL;
/*
* The submode carries the D-PHY data rate in bit/s, the only
* parameter the analog PLL needs (the same convention used to
* pass the DP link rate to PHY_MODE_DP devices).
*/
if (submode < MIPITX_RATE_MIN || submode > MIPITX_RATE_MAX) {
dev_err(phy->dev, "data rate %d bps out of range\n", submode);
return -EINVAL;
}
mipi_tx->data_rate = submode;
return 0;
}
static const struct phy_ops mtk_mipi_tx_ops = {
.init = mtk_mipi_tx_init,
.set_mode = mtk_mipi_tx_set_mode,
.power_on = mtk_mipi_tx_power_on,
.power_off = mtk_mipi_tx_power_off,
};
static int mtk_mipi_tx_probe(struct udevice *dev)
{
struct mtk_mipi_tx *mipi_tx = dev_get_priv(dev);
mipi_tx->regs = dev_read_addr_ptr(dev);
if (!mipi_tx->regs)
return -EINVAL;
return 0;
}
static const struct udevice_id mtk_mipi_tx_ids[] = {
{ .compatible = "mediatek,mt8183-mipi-tx" },
{ }
};
U_BOOT_DRIVER(mtk_mipi_tx) = {
.name = "mtk_mipi_tx",
.id = UCLASS_PHY,
.of_match = mtk_mipi_tx_ids,
.probe = mtk_mipi_tx_probe,
.ops = &mtk_mipi_tx_ops,
.priv_auto = sizeof(struct mtk_mipi_tx),
};
+11
View File
@@ -171,6 +171,17 @@ set_baud:
writel((quot >> 8) & 0xff, &priv->regs->dlm);
writel(UART_LCR_WLS_8, &priv->regs->lcr);
/*
* In low speed mode the sample count is fixed to 16 and the
* sample point and fraction registers are meaningless. Leave
* them at the values the boot firmware programmed (the same
* thing the coreboot 8250 driver does) instead of writing a
* zero sample count, which breaks the bit rate generator and
* garbles the output on boards the firmware already set up.
*/
if (samplecount <= 1)
return;
/* set highspeed mode sample count & point */
writel(samplecount - 1, &priv->regs->sample_count);
writel((samplecount >> 1) - 1, &priv->regs->sample_point);
+48 -1
View File
@@ -1022,9 +1022,16 @@ config VIDEO_MCDE_SIMPLE
before u-boot starts, and u-boot will simply render to the pre-
allocated frame buffer surface.
choice
prompt "MT8183 display driver"
depends on ARCH_MEDIATEK
help
Selects which driver drives the display pipeline on MT8183
boards: revive the pipeline initialized by the boot firmware, or
bring it up from scratch.
config VIDEO_MT8183_SCANOUT
bool "Enable MT8183 scanout driver for firmware-initialized displays"
depends on ARCH_MEDIATEK
help
Enables a display driver for the MediaTek MT8183 on boards where
the display pipeline has already been initialized by the boot
@@ -1035,6 +1042,27 @@ config VIDEO_MT8183_SCANOUT
layer address register), and lets the standard vidconsole render
into it. No display initialization is performed.
config VIDEO_MT8183_DISPLAY
bool "Enable MT8183 display pipeline bring-up driver"
depends on VIDEO_MIPI_DSI && PANEL && PHY
select VIDEO_MTK_DSI
select PHY_MTK_MIPI_TX
select PANEL_BOE_TV101WUM
help
Enables a display driver for the MediaTek MT8183 that brings the
full display pipeline up from scratch: MMSYS display clock gates,
MIPI TX D-PHY, MIPI DSI host, panel initialization (BOE
TV101WUM-NL6 and friends on the kukui family), overlay scanout of
an U-Boot-allocated framebuffer and backlight. The framebuffer is
placed outside the DRAM window described by the control device
tree and reserved in the FDT and LMB.
If the pipeline fails to come up, the driver falls back to
reviving the pipeline left running by the boot firmware (see
VIDEO_MT8183_SCANOUT) and reports which path was taken.
endchoice
config OSD
bool "Enable OSD support"
depends on DM
@@ -1439,3 +1467,22 @@ config SPL_HIDE_LOGO_VERSION
endif
endmenu
config VIDEO_MTK_DSI
bool "MediaTek MT8183 MIPI DSI host driver"
depends on ARCH_MEDIATEK && VIDEO_MIPI_DSI && PHY
select CLK
help
Enable the MediaTek MT8183 MIPI DSI host (DSI0) driver. The host
is driven in command mode to initialize the panel and switches to
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.
+4 -1
View File
@@ -80,7 +80,8 @@ obj-$(CONFIG_VIDEO_LCD_SAMSUNG_S6E63M0) += samsung-s6e63m0.o
obj-$(CONFIG_VIDEO_MCDE_SIMPLE) += mcde_simple.o
obj-${CONFIG_VIDEO_MESON} += meson/
obj-${CONFIG_VIDEO_MIPI_DSI} += mipi_dsi.o
obj-$(CONFIG_VIDEO_MT8183_SCANOUT) += mt8183_scanout.o
obj-$(CONFIG_VIDEO_MT8183_SCANOUT) += mt8183_scanout.o mt8183_disp.o
obj-$(CONFIG_VIDEO_MT8183_DISPLAY) += mt8183_display.o mt8183_disp.o
obj-$(CONFIG_VIDEO_MVEBU) += mvebu_lcd.o
obj-$(CONFIG_VIDEO_MXS) += mxsfb.o videomodes.o
obj-$(CONFIG_VIDEO_NX) += nexell_display.o videomodes.o nexell/
@@ -95,3 +96,5 @@ obj-$(CONFIG_VIDEO_ZYNQMP_DPSUB) += zynqmp/
obj-y += bridge/
obj-y += sunxi/
obj-y += tegra/
obj-$(CONFIG_VIDEO_MTK_DSI) += mtk_dsi.o
obj-$(CONFIG_PANEL_BOE_TV101WUM) += panel_boe_tv101wum.o
+175
View File
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek MT8183 display helpers shared by the display drivers.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* The firmware-handoff model documented here applies to the MT8183 kukui
* family of ChromeOS devices (e.g. the Lenovo IdeaPad Duet,
* "google,krane"): the display pipeline (MMSYS -> OVL0 -> OVL0_2L ->
* RDMA -> COLOR -> DSI -> panel) is fully initialized and running by the
* time control is passed to the next boot stage, but depthcharge's
* display_cleanup() disables the overlay engines and turns the backlight
* off right before jumping to the payload.
*/
#include <dm.h>
#include <image.h>
#include <video.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/unaligned.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/sizes.h>
#include "mt8183_disp.h"
/*
* OVL0 register offsets. Source: Linux drivers/gpu/drm/mediatek/
* mtk_disp_ovl.c (DISP_REG_OVL_EN) and device-era depthcharge
* src/drivers/video/mtk_ddp.c (ChromeOS R93, the generation shipped on
* kukui); both agree on 0x000c for OVL_EN. The 2L sub-engine enable
* (DISP_REG_OVL0_2L_EN) lives in the same register block on MT8183.
* The layer-0 source address register (DISP_REG_OVL_L0_ADDR) is not reset
* by depthcharge's stop() and holds the live scanout address.
*/
#define DISP_REG_OVL_L0_ADDR 0x0f40
#define DISP_REG_OVL_EN 0x000c
#define DISP_REG_OVL0_2L_EN 0x100c
#define LB_TAG_FRAMEBUFFER 0x12
void mt8183_disp_enable_backlight(void)
{
writel(MTK_GPIO_DOUT_BIT(PAD_DISP_PWM),
(void __iomem *)MTK_GPIO_DOUT_SET(PAD_DISP_PWM));
writel(MTK_GPIO_DOUT_BIT(PAD_EN_LCD_BL),
(void __iomem *)MTK_GPIO_DOUT_SET(PAD_EN_LCD_BL));
}
int mt8183_disp_find_framebuffer(u64 table,
struct mt8183_lb_framebuffer *fb)
{
void *base = (void *)(uintptr_t)table;
u32 header_bytes, entries, i;
void *rec;
if (get_unaligned_le32(base) != 0x4f49424c) /* "LBIO" */
return -ENOENT;
header_bytes = get_unaligned_le32(base + 4);
entries = get_unaligned_le32(base + 20);
if (header_bytes < 24 || header_bytes > 4096 ||
entries == 0 || entries > 4096)
return -EINVAL;
rec = base + header_bytes;
for (i = 0; i < entries; i++) {
u32 tag = get_unaligned_le32(rec);
u32 rsize = get_unaligned_le32(rec + 4);
if (rsize < 8)
return -EINVAL;
if (tag == LB_TAG_FRAMEBUFFER) {
if (rsize < sizeof(*fb))
return -EINVAL;
memcpy(fb, rec, sizeof(*fb));
fb->physical_address =
get_unaligned_le64(rec + 8);
return 0;
}
rec += rsize;
}
return -ENOENT;
}
int mt8183_disp_setup_handoff(fdt_addr_t ovl, struct video_uc_plat *plat,
struct video_priv *uc_priv)
{
struct mt8183_lb_framebuffer fb;
u64 addr;
int ret;
/*
* Revive the pipeline: undo depthcharge's display_cleanup() by
* re-enabling the overlay engines and driving the backlight GPIOs
* high. No panel or DSI re-initialization is needed: the panel is
* powered and the DSI link stays up through the handoff.
*/
writel(1, ovl + DISP_REG_OVL_EN);
writel(1, ovl + DISP_REG_OVL0_2L_EN);
mt8183_disp_enable_backlight();
/*
* The coreboot table sits above the DRAM window described by the
* control DTB; map it before parsing.
*/
mmu_map_region(COREBOOT_TABLE_ADDR, SZ_4K, false);
ret = mt8183_disp_find_framebuffer(COREBOOT_TABLE_ADDR, &fb);
if (ret)
return log_msg_ret("lbio", ret);
if (fb.bits_per_pixel != 32 || fb.red_pos != 16 || fb.red_size != 8 ||
fb.green_pos != 8 || fb.green_size != 8 ||
fb.blue_pos != 0 || fb.blue_size != 8)
return log_msg_ret("fmt", -EOPNOTSUPP);
/*
* Use the address from the coreboot table when it is a plausible
* DRAM address (>= 1 GiB), otherwise fall back to the address the
* firmware actually programmed into the overlay, which it left in
* place across the handoff.
*/
addr = fb.physical_address;
if (addr < SZ_1G)
addr = readl(ovl + DISP_REG_OVL_L0_ADDR);
if (addr < SZ_1G)
return log_msg_ret("scanout", -ENODEV);
plat->base = addr;
plat->size = fb.bytes_per_line * fb.y_resolution;
/*
* The scanout buffer was allocated by the boot firmware and is not
* tracked by U-Boot's memory management. Without a reservation it
* shows up as conventional RAM: once the kernel's page allocator
* goes live it reuses the region and the display corrupts. Record a
* memory reservation in the control FDT and feed it to LMB, so that
* EFI page allocations skip it, the EFI DT fixup marks it reserved
* in the memory map, and the kernel honors the /memreserve of the
* DTB it receives.
*/
ret = fdt_add_mem_rsv((void *)gd->fdt_blob, plat->base, plat->size);
if (ret)
log_warning("FDT reservation for scanout buffer failed: %d\n",
ret);
else
boot_fdt_add_mem_rsv_regions(gd->fdt_blob);
/* The scanout surface is above the DTB DRAM window: map it. */
mmu_map_region(plat->base, ALIGN(plat->size, SZ_4K), false);
uc_priv->bpix = VIDEO_BPP32;
uc_priv->xsize = fb.x_resolution;
uc_priv->ysize = fb.y_resolution;
uc_priv->line_length = fb.bytes_per_line;
/*
* The BOE TV101WUM-NL6 panel is mounted rotated 270 degrees in
* the krane chassis (see the panel node's "rotation" property in
* the upstream DT). The keyboard-covered landscape orientation
* therefore needs the console rotated; the uclass then binds the
* rotated vidconsole3 text driver. rot=1 (90 degrees) was tried
* first and read upside down on the device.
*/
uc_priv->rot = 3;
return 0;
}
+89
View File
@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MediaTek MT8183 display helpers shared by the display drivers.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*/
#ifndef _MT8183_DISP_H
#define _MT8183_DISP_H
#include <video.h>
/*
* Backlight GPIOs. The MT8183 GPIO controller is at 0x10005000; the dout
* block starts at +0x100 with 16 bytes per 32-pin group and set@+4 (layout
* of GpioRegs/GpioValRegs in device-era depthcharge src/drivers/gpio/
* mt8183.h). On kukui the backlight is driven by two dedicated GPIOs:
* DISP_PWM (pin 43) and EN_LCD_BL (PERIPHERAL_EN13, pin 176); depthcharge's
* kukui_backlight_update() drives both high to turn the backlight on.
*/
#define MTK_GPIO_BASE 0x10005000
#define MTK_GPIO_DOUT_SET(pin) (MTK_GPIO_BASE + 0x100 + ((pin) / 32) * 16 + 4)
#define MTK_GPIO_DOUT_BIT(pin) BIT((pin) % 32)
#define PAD_DISP_PWM 43
#define PAD_EN_LCD_BL 176
/*
* The coreboot table sits at a fixed address on kukui: 0xffed9000, size
* 0x380 (coreboot memlayout; confirmed on the device through
* /sys/firmware/fdt and the coreboot sysfs tags).
*/
#define COREBOOT_TABLE_ADDR 0xffed9000
struct mt8183_lb_framebuffer {
u32 tag;
u32 size;
u64 physical_address;
u32 x_resolution;
u32 y_resolution;
u32 bytes_per_line;
u8 bits_per_pixel;
u8 red_pos;
u8 red_size;
u8 green_pos;
u8 green_size;
u8 blue_pos;
u8 blue_size;
u8 reserved_pos;
u8 reserved_size;
};
/**
* mt8183_disp_enable_backlight() - drive the two kukui backlight GPIOs on
*
* Board glue for the krane backlight (DISP_PWM and EN_LCD_BL are plain
* GPIOs on this board; the upstream backlight node describes a PWM setup
* whose pin muxing the boot firmware already provides).
*/
void mt8183_disp_enable_backlight(void);
/**
* mt8183_disp_find_framebuffer() - parse the coreboot table LBIO record
*
* Layout from coreboot src/commonlib/include/commonlib/coreboot_tables.h.
* physical_address == 0 is legitimate: upstream coreboot 4.14 (the
* generation shipped on kukui) publishes the framebuffer record with
* fb_addr=0; the live scanout address then comes from OVL_L0_ADDR.
*/
int mt8183_disp_find_framebuffer(u64 table,
struct mt8183_lb_framebuffer *fb);
/**
* mt8183_disp_setup_handoff() - revive the firmware pipeline and take over
* its scanout surface
*
* Re-enables the overlay engines and the backlight (undoing depthcharge's
* display_cleanup()), discovers the geometry and the live scanout address
* from the coreboot table framebuffer record (falling back to the OVL
* layer-0 address register, which still holds the address the firmware
* used), maps the surface and reserves it in the control FDT and LMB, and
* fills @plat and @uc_priv with the resulting geometry.
*
* @ovl: OVL0 base address (already mapped)
* Return: 0 if OK, -ve on error
*/
int mt8183_disp_setup_handoff(fdt_addr_t ovl, struct video_uc_plat *plat,
struct video_priv *uc_priv);
#endif
+577
View File
@@ -0,0 +1,577 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek MT8183 display driver: full pipeline bring-up.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* On the MT8183 kukui family of ChromeOS devices (e.g. the Lenovo IdeaPad
* Duet, "google,krane") the display pipeline is
*
* MMSYS -> OVL0 -> OVL0_2L -> RDMA0 -> COLOR0 -> CCORR0 -> AAL0 ->
* GAMMA0 -> DITHER0 -> DSI0 -> panel
*
* This driver brings that pipeline up from scratch, ported from the
* ChromeOS coreboot port for this platform (src/soc/mediatek/mt8183/
* ddp.c, src/soc/mediatek/mt8183/include/soc/ddp.h and the mainboard
* display bring-up in src/mainboard/google/kukui/mainboard.c, all in
* coreboot 4.14, the generation shipped on these devices):
*
* - enable the MMSYS free-run clock gates for the display engines and
* take the SMI local arbiter out of the M4U domain (the OVL fetches
* the framebuffer directly from DRAM in U-Boot),
* - power and initialize the panel over DSI (panel uclass child of the
* DSI controller node) and start the DSI video stream,
* - route and enable the overlay chain, configure the overlay layer
* for the framebuffer surface,
* - turn the backlight on.
*
* If any stage fails (for example when the boot firmware has already
* shut the pipeline down in a way this sequence cannot recover), the
* driver falls back to reviving the pipeline left running by the boot
* firmware instead, and reports which path was taken.
*/
#include <command.h>
#include <dm.h>
#include <dsi_host.h>
#include <efi_loader.h>
#include <fdt_support.h>
#include <image.h>
#include <panel.h>
#include <video.h>
#include <asm/io.h>
#include <asm/system.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/sizes.h>
#include "mt8183_disp.h"
/*
* Base addresses of the display pipeline components. Source: coreboot
* 4.14 src/soc/mediatek/mt8183/include/soc/addressmap.h (IO_PHYS +
* offsets, matching the upstream device tree reg properties). MT8183 has
* a single MMSYS instance and one display path, so these are fixed for
* the SoC. Note the naming difference with coreboot, whose
* DISP_OVL1_BASE (0x14009000) is the OVL0_2L engine of the upstream
* device tree (node ovl_2l0@14009000).
*/
#define MMSYS_BASE 0x14000000
#define DISP_OVL0_BASE 0x14008000
#define DISP_OVL0_2L_BASE 0x14009000
#define DISP_RDMA0_BASE 0x1400b000
#define DISP_COLOR0_BASE 0x1400e000
#define DISP_CCORR0_BASE 0x1400f000
#define DISP_AAL0_BASE 0x14010000
#define DISP_GAMMA0_BASE 0x14011000
#define DISP_DITHER0_BASE 0x14012000
#define DISP_MUTEX_BASE 0x14016000
#define SMI_LARB0 0x14017000
/*
* MMSYS free-run clock gates (1 = gated). Offsets and bit assignments
* from coreboot 4.14 src/soc/mediatek/mt8183/include/soc/ddp.h
* (mmsys_cg_con0 at 0x100, mmsys_cg_con1 at 0x110); the same bits are
* modeled as CLK_MM_* gate clocks by the clock framework.
*/
#define MMSYS_CG_CON0 0x100
#define MMSYS_CG_CON1 0x110
#define CG_CON0_DISP_ALL (BIT(0) | BIT(1) | BIT(3) | BIT(4) | \
BIT(20) | BIT(21) | BIT(23) | BIT(26) | \
BIT(27) | BIT(28) | BIT(29) | BIT(30))
#define CG_CON1_DISP_DSI0 (BIT(0) | BIT(1))
/* MMSYS routing registers, from the coreboot ddp.h register map. */
#define DISP_OVL0_MOUT_EN 0xf00
#define DISP_OVL0_2L_MOUT_EN 0xf04
#define DISP_DITHER0_MOUT_EN 0xf0c
#define DISP_PATH0_SEL_IN 0xf24
#define DSI0_SEL_IN 0xf2c
#define DISP_RDMA0_SOUT_SEL_IN 0xf50
#define OVL0_MOUT_EN_OVL0_2L BIT(4)
#define OVL0_2L_MOUT_EN_DISP_PATH0 BIT(0)
#define DITHER0_MOUT_EN_DISP_DSI0 BIT(0)
#define DISP_PATH0_SEL_IN_OVL0_2L 1
#define DSI0_SEL_IN_DITHER0_MOUT 0
#define RDMA0_SOUT_SEL_IN_COLOR 1
/* DISP_MUTEX */
#define MUTEX_MOD_DISP_RDMA0 BIT(0)
#define MUTEX_MOD_DISP_OVL0 BIT(9)
#define MUTEX_MOD_DISP_OVL0_2L BIT(10)
#define MUTEX_MOD_DISP_COLOR0 BIT(13)
#define MUTEX_MOD_DISP_CCORR0 BIT(14)
#define MUTEX_MOD_DISP_AAL0 BIT(15)
#define MUTEX_MOD_DISP_GAMMA0 BIT(16)
#define MUTEX_MOD_DISP_DITHER0 BIT(17)
#define MUTEX_MOD_MAIN_PATH (MUTEX_MOD_DISP_OVL0 | \
MUTEX_MOD_DISP_OVL0_2L | \
MUTEX_MOD_DISP_RDMA0 | \
MUTEX_MOD_DISP_COLOR0 | \
MUTEX_MOD_DISP_CCORR0 | \
MUTEX_MOD_DISP_AAL0 | \
MUTEX_MOD_DISP_GAMMA0 | \
MUTEX_MOD_DISP_DITHER0)
#define MUTEX_SOF_DSI0 1
/*
* OVL registers, from the coreboot ddp_common.h register map (struct
* disp_ovl_regs). MT8183 has 4-layer OVL engines; the main path uses
* layer 0 of OVL0 for the framebuffer.
*/
#define DISP_REG_OVL_EN 0x000c
#define DISP_REG_OVL_ROI_SIZE 0x0020
#define DISP_REG_OVL_DATAPATH_CON 0x0024
#define DISP_REG_OVL_ROI_BGCLR 0x0028
#define DISP_REG_OVL_SRC_CON 0x002c
#define DISP_REG_OVL_L0_CON 0x0030
#define DISP_REG_OVL_L0_SRC_SIZE 0x0038
#define DISP_REG_OVL_L0_PITCH 0x0044
#define DISP_REG_OVL_L0_RDMA_CTRL 0x0070
#define DISP_REG_OVL_L0_RDMA_GMC 0x0078
#define DISP_REG_OVL_L0_ADDR 0x0f40
#define OVL_INFMT_RGBA8888 2
#define RDMA_MEM_GMC 0x40402020
/* DISP_RDMA0 registers, from the coreboot ddp_common.h register map. */
#define DISP_REG_RDMA_GLOBAL_CON 0x0010
#define DISP_REG_RDMA_SIZE_CON_0 0x0014
#define DISP_REG_RDMA_SIZE_CON_1 0x0018
#define DISP_REG_RDMA_FIFO_CON 0x0040
#define RDMA_ENGINE_EN BIT(0)
#define RDMA_FIFO_UNDERFLOW_EN BIT(31)
#define RDMA_FIFO_PSEUDO_SIZE(bytes) (((bytes) / 16) << 16)
#define RDMA_OUTPUT_VALID_FIFO_THRESHOLD(bytes) ((bytes) / 16)
/* DISP_COLOR0, from the coreboot ddp_common.h register map. */
#define DISP_REG_COLOR_CFG_MAIN 0x0400
#define DISP_REG_COLOR_START 0x0c00
#define DISP_REG_COLOR_WIDTH 0x0c50
#define DISP_REG_COLOR_HEIGHT 0x0c54
#define COLOR_BYPASS_ALL BIT(7)
#define COLOR_SEQ_SEL BIT(13)
/*
* The display "post processing" blocks (CCORR/AAL/GAMMA/DITHER) share
* this layout (struct disp_pq_regs of the coreboot ddp.h): enable at
* +0x0, config at +0x1c, size at +0x30.
*/
#define DISP_REG_PQ_EN 0x0000
#define DISP_REG_PQ_CFG 0x001c
#define DISP_REG_PQ_SIZE 0x0030
#define PQ_EN BIT(0)
#define PQ_RELAY_MODE BIT(0)
#define SMI_LARB_NON_SEC_CON 0x380
/*
* The RDMA pseudo FIFO size: the mediatek,rdma-fifo-size property of the
* RDMA0 node in the upstream device tree (5 KiB, same value as the
* coreboot port passes to rdma_config()).
*/
#define RDMA_FIFO_SIZE (5 * 1024)
/*
* The framebuffer sits inside the DRAM window the control device tree
* describes (2 GiB starting at DRAM_BASE), which both the LMB and EFI
* memory maps cover. The region is handed to the OS as reserved: see
* the STAGE_PIPELINE comment.
*/
#define DRAM_BASE 0x40000000
#define FB_ADDR 0xbe000000
enum mt8183_disp_stage {
STAGE_CLOCKS,
STAGE_PANEL,
STAGE_DSI_INIT,
STAGE_DSI_ENABLE,
STAGE_PIPELINE,
STAGE_COUNT,
};
static const char *const stage_names[STAGE_COUNT] = {
"clocks", "panel", "dsi-init", "dsi-enable", "pipeline",
};
struct mt8183_display_priv {
struct udevice *dsi_host;
struct udevice *panel;
struct mipi_dsi_device device;
struct display_timing timing;
};
static void mt8183_disp_clock_on(void)
{
void __iomem *mmsys = (void __iomem *)MMSYS_BASE;
clrbits_le32(mmsys + MMSYS_CG_CON0, CG_CON0_DISP_ALL);
clrbits_le32(mmsys + MMSYS_CG_CON1, CG_CON1_DISP_DSI0);
/*
* Turn off the M4U port of SMI LARB0: the OVL reads DRAM
* directly in U-Boot, without an IOMMU.
*/
writel(0, (void __iomem *)(SMI_LARB0 + SMI_LARB_NON_SEC_CON));
}
static void disp_config_main_path_connection(void)
{
void __iomem *mmsys = (void __iomem *)MMSYS_BASE;
writel(OVL0_MOUT_EN_OVL0_2L, mmsys + DISP_OVL0_MOUT_EN);
writel(OVL0_2L_MOUT_EN_DISP_PATH0, mmsys + DISP_OVL0_2L_MOUT_EN);
writel(DISP_PATH0_SEL_IN_OVL0_2L, mmsys + DISP_PATH0_SEL_IN);
writel(RDMA0_SOUT_SEL_IN_COLOR, mmsys + DISP_RDMA0_SOUT_SEL_IN);
writel(DITHER0_MOUT_EN_DISP_DSI0, mmsys + DISP_DITHER0_MOUT_EN);
writel(DSI0_SEL_IN_DITHER0_MOUT, mmsys + DSI0_SEL_IN);
}
static void disp_config_main_path_mutex(void)
{
void __iomem *mutex = (void __iomem *)DISP_MUTEX_BASE;
/* mutex[0]: EN at +0x20, CTL at +0x2c, MOD at +0x30. */
writel(MUTEX_MOD_MAIN_PATH, mutex + 0x30);
writel(MUTEX_SOF_DSI0 | (MUTEX_SOF_DSI0 << 6), mutex + 0x2c);
writel(BIT(0), mutex + 0x20);
}
static void enable_pq(void __iomem *regs, u32 width, u32 height,
int enable_relay)
{
writel(height << 16 | width, regs + DISP_REG_PQ_SIZE);
if (enable_relay)
writel(PQ_RELAY_MODE, regs + DISP_REG_PQ_CFG);
writel(PQ_EN, regs + DISP_REG_PQ_EN);
}
static void mt8183_disp_pipeline_setup(u32 width, u32 height,
u32 frame_addr)
{
void __iomem *ovl0 = (void __iomem *)DISP_OVL0_BASE;
void __iomem *ovl0_2l = (void __iomem *)DISP_OVL0_2L_BASE;
void __iomem *rdma0 = (void __iomem *)DISP_RDMA0_BASE;
void __iomem *color0 = (void __iomem *)DISP_COLOR0_BASE;
u32 pixel_clk = width * height * 60;
u32 threshold;
/*
* ROI of the two overlays of the main path; OVL0 paints its
* background opaque (same background color as the coreboot
* port), OVL0_2L passes the stream through.
*/
writel(height << 16 | width, ovl0 + DISP_REG_OVL_ROI_SIZE);
writel(0xff0000ff, ovl0 + DISP_REG_OVL_ROI_BGCLR);
writel(height << 16 | width, ovl0_2l + DISP_REG_OVL_ROI_SIZE);
writel(0, ovl0_2l + DISP_REG_OVL_ROI_BGCLR);
/* RDMA0: fetch size and FIFO thresholds. */
writel(width, rdma0 + DISP_REG_RDMA_SIZE_CON_0);
writel(height, rdma0 + DISP_REG_RDMA_SIZE_CON_1);
/*
* Enable FIFO underflow since DSI can't be blocked. Set the
* output threshold to 6 microseconds with 7/6 overhead to
* account for blanking, and with a pixel depth of 4 bytes
* (from the coreboot rdma_config()).
*/
threshold = pixel_clk * 4 * 7 / 1000;
if (threshold > RDMA_FIFO_SIZE)
threshold = RDMA_FIFO_SIZE;
writel(RDMA_FIFO_UNDERFLOW_EN |
RDMA_FIFO_PSEUDO_SIZE(RDMA_FIFO_SIZE) |
RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold),
rdma0 + DISP_REG_RDMA_FIFO_CON);
writel(RDMA_ENGINE_EN, rdma0 + DISP_REG_RDMA_GLOBAL_CON);
/* COLOR0: bypass all color processing. */
writel(width, color0 + DISP_REG_COLOR_WIDTH);
writel(height, color0 + DISP_REG_COLOR_HEIGHT);
writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL,
color0 + DISP_REG_COLOR_CFG_MAIN);
writel(BIT(0), color0 + DISP_REG_COLOR_START);
/* Post processing blocks: relay the stream through. */
enable_pq((void __iomem *)DISP_CCORR0_BASE, width, height, 1);
enable_pq((void __iomem *)DISP_AAL0_BASE, width, height, 0);
enable_pq((void __iomem *)DISP_GAMMA0_BASE, width, height, 0);
enable_pq((void __iomem *)DISP_DITHER0_BASE, width, height, 1);
/* Routing of the main path and its trigger (DSI vsync). */
disp_config_main_path_connection();
disp_config_main_path_mutex();
/*
* Overlay layer 0: the U-Boot framebuffer, 32-bit XRGB. The
* input format value is the one the coreboot port programs for
* the 32-bit framebuffer surface.
*/
writel(OVL_INFMT_RGBA8888 << 12, ovl0 + DISP_REG_OVL_L0_CON);
writel(height << 16 | width, ovl0 + DISP_REG_OVL_L0_SRC_SIZE);
writel((width * 4) & 0xffff, ovl0 + DISP_REG_OVL_L0_PITCH);
writel(frame_addr, ovl0 + DISP_REG_OVL_L0_ADDR);
writel(BIT(0), ovl0 + DISP_REG_OVL_L0_RDMA_CTRL);
writel(RDMA_MEM_GMC, ovl0 + DISP_REG_OVL_L0_RDMA_GMC);
writel(BIT(0), ovl0 + DISP_REG_OVL_SRC_CON);
/* Select the background color of OVL0_2L from its input. */
setbits_le32(ovl0_2l + DISP_REG_OVL_DATAPATH_CON, BIT(2));
/*
* The pass-through overlay must have no layers of its own: the
* boot firmware leaves its layer configuration in place across
* the handoff, and a stale OVL0_2L layer would blend leftover
* DRAM contents on top of the U-Boot framebuffer.
*/
writel(0, ovl0_2l + DISP_REG_OVL_SRC_CON);
/* Enable the overlay engines. */
writel(1, ovl0 + DISP_REG_OVL_EN);
writel(1, ovl0_2l + DISP_REG_OVL_EN);
}
static int mt8183_display_bringup(struct udevice *dev,
struct video_uc_plat *plat,
struct video_priv *uc_priv,
const char **fail_stage)
{
struct mt8183_display_priv *priv = dev_get_priv(dev);
struct mipi_dsi_panel_plat *mplat;
int stage, ret = 0;
for (stage = STAGE_CLOCKS; stage < STAGE_COUNT && !ret; stage++) {
*fail_stage = stage_names[stage];
switch (stage) {
case STAGE_CLOCKS:
/*
* Power sequencing note: the display MTCMOS power
* domain (SCPSYS) is enabled by the boot firmware
* and stays on through the handoff; U-Boot has no
* SCPSYS driver for MT8183 to (re-)enable it.
*/
mt8183_disp_clock_on();
break;
case STAGE_PANEL:
ret = uclass_first_device_err(UCLASS_PANEL,
&priv->panel);
if (ret)
break;
mplat = dev_get_plat(priv->panel);
/*
* Retrieve the panel data link characteristics and
* publish the DSI device the panel driver sends its
* commands through.
*/
priv->device.dev = priv->panel;
priv->device.lanes = mplat->lanes;
priv->device.format = mplat->format;
priv->device.mode_flags = mplat->mode_flags;
mplat->device = &priv->device;
break;
case STAGE_DSI_INIT:
ret = uclass_get_device(UCLASS_DSI_HOST, 0,
&priv->dsi_host);
if (ret)
break;
ret = panel_get_display_timing(priv->panel,
&priv->timing);
if (ret)
break;
ret = dsi_host_init(priv->dsi_host, &priv->device,
&priv->timing, 4, NULL);
break;
case STAGE_DSI_ENABLE:
/*
* Runs the panel power sequence and the
* manufacturer init commands over DSI, in
* command mode.
*/
ret = panel_enable_backlight(priv->panel);
if (ret)
break;
/* Switch the host to video mode and stream. */
ret = dsi_host_enable(priv->dsi_host);
break;
case STAGE_PIPELINE:
/*
* Allocate the framebuffer surface and make sure
* the OS never reuses it. A plain /memreserve/
* entry is not an option: libfdt splices entries
* by shifting the structure block, which needs
* slack the relocated control FDT does not have
* (fdt_splice_() fails with -ENOSPC). Instead:
*
* - allocate the region as reserved through the
* EFI allocation API, which marks it in both
* LMB and the EFI memory map; the arm64 EFI
* stub derives the kernel memory layout from
* the latter, and
* - shrink the memory bank of the control FDT
* for kernels booted without EFI, replacing
* the reg property in place, which needs no
* slack.
*/
int mem;
plat->base = FB_ADDR;
plat->size = priv->timing.hactive.typ * 4 *
priv->timing.vactive.typ;
if (CONFIG_IS_ENABLED(EFI_LOADER)) {
u64 fb = plat->base;
efi_allocate_pages(EFI_ALLOCATE_ADDRESS,
EFI_RESERVED_MEMORY_TYPE,
efi_size_in_pages(plat->size),
&fb);
}
mem = fdt_path_offset((void *)gd->fdt_blob,
"/memory");
if (mem >= 0) {
fdt64_t reg[2];
reg[0] = cpu_to_fdt64(DRAM_BASE);
reg[1] = cpu_to_fdt64(FB_ADDR - DRAM_BASE);
fdt_setprop((void *)gd->fdt_blob, mem, "reg",
reg, sizeof(reg));
}
mmu_map_region(plat->base,
ALIGN(plat->size, SZ_4K), false);
mt8183_disp_pipeline_setup(priv->timing.hactive.typ,
priv->timing.vactive.typ,
plat->base);
mt8183_disp_enable_backlight();
break;
}
}
if (ret)
return ret;
uc_priv->bpix = VIDEO_BPP32;
uc_priv->xsize = priv->timing.hactive.typ;
uc_priv->ysize = priv->timing.vactive.typ;
uc_priv->line_length = uc_priv->xsize * 4;
uc_priv->rot = dev_read_u32_default(priv->panel, "rotation", 0) / 90;
return 0;
}
static int mt8183_display_probe(struct udevice *dev)
{
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
const char *fail_stage = "start";
fdt_addr_t ovl;
int ret;
ovl = dev_read_addr(dev);
if (ovl == FDT_ADDR_T_NONE)
return log_msg_ret("ovl", -EINVAL);
printf("[dsi] phase 0: bring-up start\n");
ret = mt8183_display_bringup(dev, plat, uc_priv, &fail_stage);
if (!ret) {
printf("[dsi] phase 9: full bring-up done\n");
printf("Video: MT8183 display %dx%d@32bpp at %llx (cold bring-up)\n",
uc_priv->xsize, uc_priv->ysize,
(unsigned long long)plat->base);
/*
* The framebuffer is ordinary cacheable DRAM that the overlay
* reads through DMA: make video_sync() flush the dirty cache
* lines, or freshly drawn console output only becomes visible
* when the cache happens to evict them.
*/
video_set_flush_dcache(dev, true);
return 0;
}
/*
* Fall back to reviving the pipeline left running by the boot
* firmware.
*/
printf("[dsi] phase F: bring-up failed at stage %s (%d), falling back to firmware handoff\n",
fail_stage, ret);
ret = mt8183_disp_setup_handoff(ovl, plat, uc_priv);
if (ret)
return ret;
printf("Video: MT8183 scanout %dx%d@32bpp at %llx (firmware handoff)\n",
uc_priv->xsize, uc_priv->ysize,
(unsigned long long)plat->base);
video_set_flush_dcache(dev, true);
return 0;
}
static const struct udevice_id mt8183_display_ids[] = {
{ .compatible = "mediatek,mt8183-disp-ovl" },
{ }
};
U_BOOT_DRIVER(mt8183_display) = {
.name = "mt8183_display",
.id = UCLASS_VIDEO,
.of_match = mt8183_display_ids,
.probe = mt8183_display_probe,
.priv_auto = sizeof(struct mt8183_display_priv),
};
/*
* TEMPORARY DIAGNOSTIC - DO NOT COMMIT.
* Prints an ASCII luminance map of the framebuffer (16x16 px blocks)
* over the console, in landscape orientation.
*/
static int do_fbmap(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct udevice *vid;
struct video_priv *priv;
u32 *fb;
int fx, fy, i, j, b, maxcol;
uclass_first_device(UCLASS_VIDEO, &vid);
if (!vid)
return CMD_RET_FAILURE;
priv = dev_get_uclass_priv(vid);
fb = (u32 *)priv->fb;
b = argc > 1 ? simple_strtoul(argv[1], NULL, 10) : 16;
maxcol = argc > 2 ? simple_strtoul(argv[2], NULL, 10) : priv->xsize;
if (b < 1 || b > 256 || maxcol < b || maxcol > priv->xsize)
return CMD_RET_USAGE;
printf("fb %dx%d ll=%d rot=%d @p %08llx b=%d maxcol=%d\n",
priv->xsize, priv->ysize, priv->line_length, priv->rot,
(unsigned long long)(uintptr_t)priv->fb, b, maxcol);
for (j = 0; j < priv->ysize / b; j++) {
for (i = 0; i < maxcol / b; i++) {
unsigned sum = 0;
char c;
for (fy = 0; fy < b; fy++)
for (fx = 0; fx < b; fx++)
sum += (fb[(j * b + fy) *
priv->line_length / 4 +
i * b + fx] >> 8) & 0xff;
sum /= b * b;
c = sum < 16 ? '.' : sum < 64 ? '-' : sum < 128 ? '+' : '#';
putc(c);
}
putc('\n');
}
return CMD_RET_SUCCESS;
}
U_BOOT_CMD(fbmap, 3, 1, do_fbmap,
"print ASCII luminance map of the framebuffer",
"[block_px] [max_col_px]");
+6 -168
View File
@@ -16,193 +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 <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 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;
return ret;
printf("Video: MT8183 scanout %dx%d@32bpp at %llx\n",
uc_priv->xsize, uc_priv->ysize, (unsigned long long)plat->base);
+666
View File
@@ -0,0 +1,666 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek MT8183 MIPI DSI host driver.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* Ported from the Linux kernel driver drivers/gpu/drm/mediatek/mtk_dsi.c
* (register map and D-PHY timing computation) and from the ChromeOS
* coreboot port src/soc/mediatek/common/dsi.c (video mode timing and the
* command FIFO programming), the two implementations proven to drive
* this host controller on the kukui family of boards.
*/
#include <clk.h>
#include <dm.h>
#include <dsi_host.h>
#include <generic-phy.h>
#include <asm/io.h>
#include <dm/device_compat.h>
#include <linux/bitops.h>
#include <div64.h>
#include <linux/iopoll.h>
/* DSI_START */
#define DSI_START 0x00
/* DSI_INTSTA */
#define DSI_INTSTA 0x0c
#define LPRX_RD_RDY_INT_FLAG BIT(0)
#define CMD_DONE_INT_FLAG BIT(1)
#define VM_DONE_INT_FLAG BIT(3)
#define DSI_BUSY BIT(31)
/* DSI_CON_CTRL */
#define DSI_CON_CTRL 0x10
#define DSI_RESET BIT(0)
#define DSI_EN BIT(1)
#define DPHY_RESET BIT(2)
/* DSI_MODE_CTRL */
#define DSI_MODE_CTRL 0x14
#define CMD_MODE 0
#define SYNC_PULSE_MODE 1
#define SYNC_EVENT_MODE 2
#define BURST_MODE 3
/* DSI_TXRX_CTRL */
#define DSI_TXRX_CTRL 0x18
#define EOTP_DISABLE BIT(6)
#define NON_CONTINUOUS_CLK BIT(16)
/* DSI_PSCTRL */
#define DSI_PSCTRL 0x1c
#define DSI_PS_WC 0x3fff
#define PACKED_PS_16BIT_RGB565 (0 << 16)
#define LOOSELY_PS_18BIT_RGB666 (0x1 << 16)
#define PACKED_PS_18BIT_RGB666 (0x2 << 16)
#define PACKED_PS_24BIT_RGB888 (0x3 << 16)
#define PIXEL_STREAM_CUSTOM_HEADER_SHIFT 26
#define PIXEL_STREAM_CUSTOM_HEADER 0xb
/* DSI_SIZE_CON */
#define DSI_SIZE_CON 0x38
#define DSI_SIZE_CON_HEIGHT_SHIFT 16
#define DSI_SIZE_CON_WIDTH_SHIFT 0
/* Per-line word counts */
#define DSI_VSA_NL 0x20
#define DSI_VBP_NL 0x24
#define DSI_VFP_NL 0x28
#define DSI_VACT_NL 0x2c
#define DSI_HSA_WC 0x50
#define DSI_HBP_WC 0x54
#define DSI_HFP_WC 0x58
/* DSI_CMDQ_SIZE */
#define DSI_CMDQ_SIZE 0x60
#define CMDQ_SIZE 0x3f
/* DSI_PHY_LCCON */
#define DSI_PHY_LCCON 0x104
#define LC_HS_TX_EN BIT(0)
/* DSI_PHY_TIMECON0..3 */
#define DSI_PHY_TIMECON0 0x110
#define DSI_PHY_TIMECON1 0x114
#define DSI_PHY_TIMECON2 0x118
#define DSI_PHY_TIMECON3 0x11c
/* DSI_CMDQ0 */
#define DSI_CMDQ0 0x200
#define CONFIG (0xff << 0)
#define SHORT_PACKET 0
#define LONG_PACKET 2
#define BTA BIT(2)
#define HSTX BIT(3)
#define DATA_ID (0xff << 8)
#define DATA_0 (0xff << 16)
#define DATA_1 (0xff << 24)
/* DSI_RX_DATA0 */
#define DSI_RX_DATA0 0x74
/* DSI_FORCE_COMMIT */
#define DSI_FORCE_COMMIT 0x190
#define DSI_FORCE_COMMIT_USE_MMSYS BIT(0)
#define DSI_FORCE_COMMIT_ALWAYS BIT(1)
#define MIN_HFP_BYTE 2
#define MIN_HBP_BYTE 2
#define CMD_DONE_TIMEOUT_US 20000
struct mtk_phy_timing {
u32 lpx;
u32 da_hs_prepare;
u32 da_hs_zero;
u32 da_hs_trail;
u32 ta_go;
u32 ta_sure;
u32 ta_get;
u32 da_hs_exit;
u32 da_hs_sync;
u32 clk_hs_prepare;
u32 clk_hs_post;
u32 clk_hs_trail;
u32 clk_hs_zero;
u32 clk_hs_exit;
};
struct mtk_dsi {
void __iomem *regs;
struct mipi_dsi_host host;
struct mipi_dsi_device *device;
struct display_timing timing;
struct clk clk_engine;
struct clk clk_digital;
struct phy dphy;
bool phy_on;
bool video_mode;
u32 lanes;
u32 data_rate;
struct mtk_phy_timing phy_timing;
};
static inline struct mtk_dsi *host_to_mtk_dsi(struct mipi_dsi_host *host)
{
return container_of(host, struct mtk_dsi, host);
}
static u32 mtk_dsi_bits_per_pixel(enum mipi_dsi_pixel_format format)
{
switch (format) {
case MIPI_DSI_FMT_RGB565:
return 16;
case MIPI_DSI_FMT_RGB666_PACKED:
return 18;
case MIPI_DSI_FMT_RGB666:
case MIPI_DSI_FMT_RGB888:
default:
return 24;
}
}
static void mtk_dsi_start(struct mtk_dsi *dsi)
{
writel(0, dsi->regs + DSI_START);
writel(1, dsi->regs + DSI_START);
}
static void mtk_dsi_stop(struct mtk_dsi *dsi)
{
writel(0, dsi->regs + DSI_START);
}
/*
* D-PHY timing computation, from mtk_dsi_phy_timing() in both the Linux
* driver and the coreboot port. The values are in units of byte clocks at
* the selected data rate.
*/
static void mtk_dsi_phy_timing(struct mtk_dsi *dsi)
{
struct mtk_phy_timing *t = &dsi->phy_timing;
u32 data_rate_mhz = DIV_ROUND_UP(dsi->data_rate, 1000000);
u32 timcon0, timcon1, timcon2, timcon3;
memset(t, 0, sizeof(*t));
t->lpx = (60 * data_rate_mhz / (8 * 1000)) + 1;
t->da_hs_prepare = (80 * data_rate_mhz + 4 * 1000) / 8000;
t->da_hs_zero = (170 * data_rate_mhz + 10 * 1000) / 8000 + 1 -
t->da_hs_prepare;
t->da_hs_trail = t->da_hs_prepare + 1;
t->ta_go = 4 * t->lpx - 2;
t->ta_sure = t->lpx + 2;
t->ta_get = 4 * t->lpx;
t->da_hs_exit = 2 * t->lpx + 1;
t->da_hs_sync = 1;
t->clk_hs_prepare = 70 * data_rate_mhz / (8 * 1000);
t->clk_hs_post = t->clk_hs_prepare + 8;
t->clk_hs_trail = t->clk_hs_prepare;
t->clk_hs_zero = t->clk_hs_trail * 4;
t->clk_hs_exit = 2 * t->clk_hs_trail;
timcon0 = t->lpx | t->da_hs_prepare << 8 | t->da_hs_zero << 16 |
t->da_hs_trail << 24;
timcon1 = t->ta_go | t->ta_sure << 8 | t->ta_get << 16 |
t->da_hs_exit << 24;
timcon2 = t->da_hs_sync << 8 | t->clk_hs_zero << 16 |
t->clk_hs_trail << 24;
timcon3 = t->clk_hs_prepare | t->clk_hs_post << 8 |
t->clk_hs_exit << 16;
writel(timcon0, dsi->regs + DSI_PHY_TIMECON0);
writel(timcon1, dsi->regs + DSI_PHY_TIMECON1);
writel(timcon2, dsi->regs + DSI_PHY_TIMECON2);
writel(timcon3, dsi->regs + DSI_PHY_TIMECON3);
}
static void mtk_dsi_reset(struct mtk_dsi *dsi)
{
/*
* 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.
*/
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)
{
setbits_le32(dsi->regs + DSI_CON_CTRL, DPHY_RESET);
clrbits_le32(dsi->regs + DSI_CON_CTRL, DPHY_RESET);
}
static void mtk_dsi_rxtx_control(struct mtk_dsi *dsi)
{
u32 val;
switch (dsi->lanes) {
case 1:
val = 1 << 2;
break;
case 2:
val = 3 << 2;
break;
case 3:
val = 7 << 2;
break;
case 4:
default:
val = 0xf << 2;
break;
}
if (dsi->device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
val |= NON_CONTINUOUS_CLK;
/*
* EOT packets are disabled unless the panel requests them (the
* same logic as mtk_dsi_rxtx_control() in the coreboot port).
*/
if (!(dsi->device->mode_flags & MIPI_DSI_MODE_EOT_PACKET))
val |= EOTP_DISABLE;
writel(val, dsi->regs + DSI_TXRX_CTRL);
}
static void mtk_dsi_clk_hs_mode_enable(struct mtk_dsi *dsi)
{
setbits_le32(dsi->regs + DSI_PHY_LCCON, LC_HS_TX_EN);
}
static void mtk_dsi_clk_hs_mode_disable(struct mtk_dsi *dsi)
{
clrbits_le32(dsi->regs + DSI_PHY_LCCON, LC_HS_TX_EN);
}
static void mtk_dsi_set_mode(struct mtk_dsi *dsi, u32 mode_flags)
{
u32 val = CMD_MODE;
if (mode_flags & MIPI_DSI_MODE_VIDEO) {
if (mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
val = BURST_MODE;
else if (mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
val = SYNC_PULSE_MODE;
else
val = SYNC_EVENT_MODE;
}
writel(val, dsi->regs + DSI_MODE_CTRL);
}
static void mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
{
struct display_timing *timing = &dsi->timing;
u32 bpp = mtk_dsi_bits_per_pixel(dsi->device->format);
u32 bytes_per_pixel = DIV_ROUND_UP(bpp, 8);
u32 hsync_active_byte, hbp_byte, hfp_byte, data_phy_cycles, d_phy;
u32 packet_fmt, hactive;
s32 hfp_adjust, hbp_adjust;
writel(timing->vsync_len.typ, dsi->regs + DSI_VSA_NL);
writel(timing->vback_porch.typ, dsi->regs + DSI_VBP_NL);
writel(timing->vfront_porch.typ, dsi->regs + DSI_VFP_NL);
writel(timing->vactive.typ, dsi->regs + DSI_VACT_NL);
hsync_active_byte = timing->hsync_len.typ * bytes_per_pixel - 10;
if (dsi->device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
hbp_byte = timing->hback_porch.typ * bytes_per_pixel - 10;
else
hbp_byte = (timing->hback_porch.typ +
timing->hsync_len.typ) * bytes_per_pixel - 10;
hfp_byte = timing->hfront_porch.typ * bytes_per_pixel;
/*
* The D-PHY needs some of the horizontal blanking to switch
* between LP and HS, from mtk_dsi_config_vdo_timing() in the
* coreboot port.
*/
data_phy_cycles = dsi->phy_timing.lpx + dsi->phy_timing.da_hs_prepare +
dsi->phy_timing.da_hs_zero +
dsi->phy_timing.da_hs_exit + 3;
d_phy = data_phy_cycles * dsi->lanes + 10;
hfp_adjust = (s32)timing->hfront_porch.typ;
hbp_adjust = (s32)timing->hback_porch.typ;
if ((hfp_adjust + hbp_adjust) * (s32)bytes_per_pixel > (s32)d_phy) {
hfp_byte -= d_phy * hfp_adjust / (hfp_adjust + hbp_adjust);
hbp_byte -= d_phy * hbp_adjust / (hfp_adjust + hbp_adjust);
} else {
dev_warn(dsi->host.dev,
"HFP + HBP too small for the D-PHY turnaround, the panel may misbehave\n");
}
if (hfp_byte + hbp_byte < MIN_HFP_BYTE + MIN_HBP_BYTE) {
dev_warn(dsi->host.dev,
"HFP/HBP too small, the panel may misbehave\n");
} else if (hfp_byte < MIN_HFP_BYTE) {
hbp_byte -= MIN_HFP_BYTE - hfp_byte;
hfp_byte = MIN_HFP_BYTE;
} else if (hbp_byte < MIN_HBP_BYTE) {
hfp_byte -= MIN_HBP_BYTE - hbp_byte;
hbp_byte = MIN_HBP_BYTE;
}
writel(hsync_active_byte, dsi->regs + DSI_HSA_WC);
writel(hbp_byte, dsi->regs + DSI_HBP_WC);
writel(hfp_byte, dsi->regs + DSI_HFP_WC);
switch (dsi->device->format) {
case MIPI_DSI_FMT_RGB565:
packet_fmt = PACKED_PS_16BIT_RGB565;
break;
case MIPI_DSI_FMT_RGB666:
packet_fmt = LOOSELY_PS_18BIT_RGB666;
break;
case MIPI_DSI_FMT_RGB666_PACKED:
packet_fmt = PACKED_PS_18BIT_RGB666;
break;
case MIPI_DSI_FMT_RGB888:
default:
packet_fmt = PACKED_PS_24BIT_RGB888;
break;
}
hactive = timing->hactive.typ;
packet_fmt |= (hactive * bytes_per_pixel) & DSI_PS_WC;
writel(PIXEL_STREAM_CUSTOM_HEADER << PIXEL_STREAM_CUSTOM_HEADER_SHIFT |
packet_fmt, dsi->regs + DSI_PSCTRL);
writel(timing->vactive.typ << DSI_SIZE_CON_HEIGHT_SHIFT |
hactive << DSI_SIZE_CON_WIDTH_SHIFT,
dsi->regs + DSI_SIZE_CON);
}
static bool mtk_dsi_is_read_command(u32 type)
{
switch (type) {
case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
case MIPI_DSI_DCS_READ:
return true;
default:
return false;
}
}
static int mtk_dsi_wait_not_busy(struct mtk_dsi *dsi)
{
u32 sta;
return readl_poll_timeout(dsi->regs + DSI_INTSTA, sta,
!(sta & DSI_BUSY), CMD_DONE_TIMEOUT_US);
}
static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
{
struct mtk_dsi *dsi = host_to_mtk_dsi(host);
const u8 *tx_buf = msg->tx_buf;
u32 config, reg_val, cmdq_size, sta;
bool was_video_mode = dsi->video_mode;
ssize_t ret = 0;
int i, j;
if (was_video_mode) {
mtk_dsi_stop(dsi);
mtk_dsi_set_mode(dsi, 0); /* back to command mode */
}
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;
}
writel(LPRX_RD_RDY_INT_FLAG | CMD_DONE_INT_FLAG | VM_DONE_INT_FLAG,
dsi->regs + DSI_INTSTA);
if (mtk_dsi_is_read_command(msg->type))
config = BTA;
else
config = (msg->tx_len > 2) ? LONG_PACKET : SHORT_PACKET;
if (!(msg->flags & MIPI_DSI_MSG_USE_LPM))
config |= HSTX;
if (msg->tx_len > 2) {
/*
* Long packet: the first command queue word carries the
* configuration and the packet header, the following
* words carry the payload, 4 bytes each.
*/
reg_val = ((u32)msg->tx_len << 16) | ((u32)msg->type << 8) |
config;
writel(reg_val, dsi->regs + DSI_CMDQ0);
for (i = 0; i < msg->tx_len; i += 4) {
u32 val = 0;
for (j = 0; j < 4 && i + j < msg->tx_len; j++)
val |= (u32)tx_buf[i + j] << (j * 8);
writel(val, dsi->regs + DSI_CMDQ0 + 4 + (i / 4) * 4);
}
cmdq_size = 1 + DIV_ROUND_UP(msg->tx_len, 4);
} else {
/* Short packet: parameters packed in the header word. */
reg_val = ((u32)msg->type << 8) | config;
for (i = 0; i < msg->tx_len; i++)
reg_val |= (u32)tx_buf[i] << ((i + 2) * 8);
writel(reg_val, dsi->regs + DSI_CMDQ0);
cmdq_size = 1;
}
writel(cmdq_size, dsi->regs + DSI_CMDQ_SIZE);
mtk_dsi_start(dsi);
ret = readl_poll_timeout(dsi->regs + DSI_INTSTA, sta,
sta & (CMD_DONE_INT_FLAG | LPRX_RD_RDY_INT_FLAG),
CMD_DONE_TIMEOUT_US);
if (ret) {
dev_err(host->dev, "failed to send DSI command\n");
goto out;
}
if (mtk_dsi_is_read_command(msg->type) && msg->rx_buf) {
u8 rx[16];
for (i = 0; i < 16; i++)
rx[i] = readb(dsi->regs + DSI_RX_DATA0 + i);
/*
* Short responses carry the payload from byte 1, long
* ones from byte 4 (after the 2-byte header).
*/
if (msg->rx_len > 2)
memcpy(msg->rx_buf, rx + 4,
min_t(size_t, msg->rx_len, 8));
else
memcpy(msg->rx_buf, rx + 1,
min_t(size_t, msg->rx_len, 2));
ret = msg->rx_len;
}
out:
if (was_video_mode) {
mtk_dsi_set_mode(dsi, dsi->device->mode_flags);
mtk_dsi_start(dsi);
}
return ret;
}
static int mtk_dsi_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
{
struct mtk_dsi *dsi = host_to_mtk_dsi(host);
dsi->lanes = device->lanes;
return 0;
}
static const struct mipi_dsi_host_ops mtk_dsi_host_ops = {
.attach = mtk_dsi_host_attach,
.transfer = mtk_dsi_host_transfer,
};
static int mtk_dsi_init(struct udevice *dev,
struct mipi_dsi_device *device,
struct display_timing *timings,
unsigned int max_data_lanes,
const struct mipi_dsi_phy_ops *phy_ops)
{
struct mtk_dsi *dsi = dev_get_priv(dev);
int bpp, ret;
/*
* The D-PHY is a separate device on MT8183 (the mipi_tx node);
* it is driven through the generic PHY API, so the phy_ops
* argument of the uclass is unused here.
*/
(void)phy_ops;
dsi->device = device;
dsi->lanes = device->lanes;
dsi->timing = *timings;
dsi->host.dev = (struct device *)dev;
dsi->host.ops = &mtk_dsi_host_ops;
device->host = &dsi->host;
dsi->regs = dev_read_addr_ptr(dev);
if (!dsi->regs)
return log_msg_ret("regs", -EINVAL);
if (dsi->lanes > max_data_lanes) {
dev_err(dev, "panel uses %u lanes, host supports %u\n",
dsi->lanes, max_data_lanes);
return -EINVAL;
}
ret = clk_get_by_name(dev, "engine", &dsi->clk_engine);
if (ret)
return log_msg_ret("clk-engine", ret);
ret = clk_get_by_name(dev, "digital", &dsi->clk_digital);
if (ret)
return log_msg_ret("clk-digital", ret);
ret = clk_enable(&dsi->clk_engine);
if (ret)
return log_msg_ret("clk-engine-en", ret);
ret = clk_enable(&dsi->clk_digital);
if (ret)
return log_msg_ret("clk-digital-en", ret);
ret = generic_phy_get_by_name(dev, "dphy", &dsi->dphy);
if (ret)
return log_msg_ret("phy", ret);
bpp = mipi_dsi_pixel_format_to_bpp(device->format);
if (bpp < 0)
return log_msg_ret("fmt", bpp);
dsi->data_rate = DIV_ROUND_UP_ULL((u64)timings->pixelclock.typ * bpp,
dsi->lanes);
/* Program the MIPI TX PLL for the data rate and power it up. */
ret = generic_phy_set_mode(&dsi->dphy, PHY_MODE_MIPI_DPHY,
(int)dsi->data_rate);
if (ret)
return log_msg_ret("phy-mode", ret);
ret = generic_phy_init(&dsi->dphy);
if (ret)
return log_msg_ret("phy-init", ret);
ret = generic_phy_power_on(&dsi->dphy);
if (ret)
return log_msg_ret("phy-on", ret);
dsi->phy_on = true;
/*
* From here on the sequence follows mtk_dsi_init() of the
* coreboot port (proven on this platform), with the final "switch
* to video mode and start" split out into the enable() op so that
* panel initialization commands can be sent in command mode.
*/
mtk_dsi_reset(dsi);
mtk_dsi_phy_timing(dsi);
mtk_dsi_rxtx_control(dsi);
mdelay(1);
mtk_dsi_reset_dphy(dsi);
mtk_dsi_clk_hs_mode_disable(dsi);
mtk_dsi_config_vdo_timing(dsi);
mtk_dsi_clk_hs_mode_enable(dsi);
return 0;
}
static int mtk_dsi_enable(struct udevice *dev)
{
struct mtk_dsi *dsi = dev_get_priv(dev);
/* Switch to video mode and start the stream. */
mtk_dsi_set_mode(dsi, dsi->device->mode_flags);
mtk_dsi_start(dsi);
dsi->video_mode = true;
return 0;
}
static int mtk_dsi_disable(struct udevice *dev)
{
struct mtk_dsi *dsi = dev_get_priv(dev);
if (dsi->phy_on)
generic_phy_power_off(&dsi->dphy);
dsi->phy_on = false;
clk_disable(&dsi->clk_digital);
clk_disable(&dsi->clk_engine);
return 0;
}
static int mtk_dsi_bind(struct udevice *dev)
{
/* Bind the panel child node, if any. */
return dm_scan_fdt_dev(dev);
}
static const struct udevice_id mtk_dsi_ids[] = {
{ .compatible = "mediatek,mt8183-dsi" },
{ }
};
static const struct dsi_host_ops mtk_dsi_ops = {
.init = mtk_dsi_init,
.enable = mtk_dsi_enable,
.disable = mtk_dsi_disable,
};
U_BOOT_DRIVER(mtk_dsi) = {
.name = "mtk_dsi",
.id = UCLASS_DSI_HOST,
.of_match = mtk_dsi_ids,
.bind = mtk_dsi_bind,
.ops = &mtk_dsi_ops,
.priv_auto = sizeof(struct mtk_dsi),
};
+544
View File
@@ -0,0 +1,544 @@
// SPDX-License-Identifier: GPL-2.0
/*
* BOE TV101WUM-NL6 MIPI DSI panel driver.
*
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
*
* 1200x1920 (native portrait) video-mode panel, 4 data lanes, RGB888,
* mounted rotated in the krane (Lenovo IdeaPad Duet) chassis.
*
* The power sequence and the manufacturer initialization commands are
* ported from the ChromeOS coreboot port for this exact panel,
* src/mainboard/google/kukui/panel_params/panel-BOE_TV101WUM_NL6.c in
* coreboot 4.14, which matches the sequence of the Linux kernel driver
* drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c for the
* "boe,tv101wum-nl6" entry (159.425 MHz pixel clock, 100/40/24
* horizontal and 10/14/4 vertical timing).
*/
#include <backlight.h>
#include <dm.h>
#include <mipi_dsi.h>
#include <panel.h>
#include <asm/gpio.h>
#include <dm/device_compat.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <power/regulator.h>
/*
* Manufacturer initialization command stream. Packed as
* <type> [<len> [<param>...]]
* with type one of:
* LCM_DELAY_CMD: <type> <milliseconds>
* LCM_DCS_CMD: <type> <len> <parameters...>
* Ported verbatim from the coreboot panel-BOE_TV101WUM_NL6.c table.
*/
#define LCM_END_CMD 0
#define LCM_DELAY_CMD 1
#define LCM_DCS_CMD 3
#define LCM_DELAY(ms) LCM_DELAY_CMD, (ms)
#define LCM_DCS_CMD_ENTRY(seq...) \
LCM_DCS_CMD, LCM_NPARAM(seq), seq
#define LCM_NPARAM(...) \
(sizeof((u8[]){0, ##__VA_ARGS__}) / sizeof(u8) - 1)
static const u8 boe_tv101wum_nl6_init_seq[] = {
LCM_DELAY(24),
LCM_DCS_CMD_ENTRY(0xB0, 0x05),
LCM_DCS_CMD_ENTRY(0xB1, 0xE5),
LCM_DCS_CMD_ENTRY(0xB3, 0x52),
LCM_DCS_CMD_ENTRY(0xB0, 0x00),
LCM_DCS_CMD_ENTRY(0xB3, 0x88),
LCM_DCS_CMD_ENTRY(0xB0, 0x04),
LCM_DCS_CMD_ENTRY(0xB8, 0x00),
LCM_DCS_CMD_ENTRY(0xB0, 0x00),
LCM_DCS_CMD_ENTRY(0xB6, 0x03),
LCM_DCS_CMD_ENTRY(0xBA, 0x8B),
LCM_DCS_CMD_ENTRY(0xBF, 0x1A),
LCM_DCS_CMD_ENTRY(0xC0, 0x0F),
LCM_DCS_CMD_ENTRY(0xC2, 0x0C),
LCM_DCS_CMD_ENTRY(0xC3, 0x02),
LCM_DCS_CMD_ENTRY(0xC4, 0x0C),
LCM_DCS_CMD_ENTRY(0xC5, 0x02),
LCM_DCS_CMD_ENTRY(0xB0, 0x01),
LCM_DCS_CMD_ENTRY(0xE0, 0x26),
LCM_DCS_CMD_ENTRY(0xE1, 0x26),
LCM_DCS_CMD_ENTRY(0xDC, 0x00),
LCM_DCS_CMD_ENTRY(0xDD, 0x00),
LCM_DCS_CMD_ENTRY(0xCC, 0x26),
LCM_DCS_CMD_ENTRY(0xCD, 0x26),
LCM_DCS_CMD_ENTRY(0xC8, 0x00),
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
LCM_DCS_CMD_ENTRY(0xD2, 0x03),
LCM_DCS_CMD_ENTRY(0xD3, 0x03),
LCM_DCS_CMD_ENTRY(0xE6, 0x04),
LCM_DCS_CMD_ENTRY(0xE7, 0x04),
LCM_DCS_CMD_ENTRY(0xC4, 0x09),
LCM_DCS_CMD_ENTRY(0xC5, 0x09),
LCM_DCS_CMD_ENTRY(0xD8, 0x0A),
LCM_DCS_CMD_ENTRY(0xD9, 0x0A),
LCM_DCS_CMD_ENTRY(0xC2, 0x0B),
LCM_DCS_CMD_ENTRY(0xC3, 0x0B),
LCM_DCS_CMD_ENTRY(0xD6, 0x0C),
LCM_DCS_CMD_ENTRY(0xD7, 0x0C),
LCM_DCS_CMD_ENTRY(0xC0, 0x05),
LCM_DCS_CMD_ENTRY(0xC1, 0x05),
LCM_DCS_CMD_ENTRY(0xD4, 0x06),
LCM_DCS_CMD_ENTRY(0xD5, 0x06),
LCM_DCS_CMD_ENTRY(0xCA, 0x07),
LCM_DCS_CMD_ENTRY(0xCB, 0x07),
LCM_DCS_CMD_ENTRY(0xDE, 0x08),
LCM_DCS_CMD_ENTRY(0xDF, 0x08),
LCM_DCS_CMD_ENTRY(0xB0, 0x02),
LCM_DCS_CMD_ENTRY(0xC0, 0x00),
LCM_DCS_CMD_ENTRY(0xC1, 0x0D),
LCM_DCS_CMD_ENTRY(0xC2, 0x17),
LCM_DCS_CMD_ENTRY(0xC3, 0x26),
LCM_DCS_CMD_ENTRY(0xC4, 0x31),
LCM_DCS_CMD_ENTRY(0xC5, 0x1C),
LCM_DCS_CMD_ENTRY(0xC6, 0x2C),
LCM_DCS_CMD_ENTRY(0xC7, 0x33),
LCM_DCS_CMD_ENTRY(0xC8, 0x31),
LCM_DCS_CMD_ENTRY(0xC9, 0x37),
LCM_DCS_CMD_ENTRY(0xCA, 0x37),
LCM_DCS_CMD_ENTRY(0xCB, 0x37),
LCM_DCS_CMD_ENTRY(0xCC, 0x39),
LCM_DCS_CMD_ENTRY(0xCD, 0x2E),
LCM_DCS_CMD_ENTRY(0xCE, 0x2F),
LCM_DCS_CMD_ENTRY(0xCF, 0x2F),
LCM_DCS_CMD_ENTRY(0xD0, 0x07),
LCM_DCS_CMD_ENTRY(0xD2, 0x00),
LCM_DCS_CMD_ENTRY(0xD3, 0x0D),
LCM_DCS_CMD_ENTRY(0xD4, 0x17),
LCM_DCS_CMD_ENTRY(0xD5, 0x26),
LCM_DCS_CMD_ENTRY(0xD6, 0x31),
LCM_DCS_CMD_ENTRY(0xD7, 0x3F),
LCM_DCS_CMD_ENTRY(0xD8, 0x3F),
LCM_DCS_CMD_ENTRY(0xD9, 0x3F),
LCM_DCS_CMD_ENTRY(0xDA, 0x3F),
LCM_DCS_CMD_ENTRY(0xDB, 0x37),
LCM_DCS_CMD_ENTRY(0xDC, 0x37),
LCM_DCS_CMD_ENTRY(0xDD, 0x37),
LCM_DCS_CMD_ENTRY(0xDE, 0x39),
LCM_DCS_CMD_ENTRY(0xDF, 0x2E),
LCM_DCS_CMD_ENTRY(0xE0, 0x2F),
LCM_DCS_CMD_ENTRY(0xE1, 0x2F),
LCM_DCS_CMD_ENTRY(0xE2, 0x07),
LCM_DCS_CMD_ENTRY(0xB0, 0x03),
LCM_DCS_CMD_ENTRY(0xC8, 0x0B),
LCM_DCS_CMD_ENTRY(0xC9, 0x07),
LCM_DCS_CMD_ENTRY(0xC3, 0x00),
LCM_DCS_CMD_ENTRY(0xE7, 0x00),
LCM_DCS_CMD_ENTRY(0xC5, 0x2A),
LCM_DCS_CMD_ENTRY(0xDE, 0x2A),
LCM_DCS_CMD_ENTRY(0xCA, 0x43),
LCM_DCS_CMD_ENTRY(0xC9, 0x07),
LCM_DCS_CMD_ENTRY(0xE4, 0xC0),
LCM_DCS_CMD_ENTRY(0xE5, 0x0D),
LCM_DCS_CMD_ENTRY(0xCB, 0x00),
LCM_DCS_CMD_ENTRY(0xB0, 0x06),
LCM_DCS_CMD_ENTRY(0xB8, 0xA5),
LCM_DCS_CMD_ENTRY(0xC0, 0xA5),
LCM_DCS_CMD_ENTRY(0xC7, 0x0F),
LCM_DCS_CMD_ENTRY(0xD5, 0x32),
LCM_DCS_CMD_ENTRY(0xB8, 0x00),
LCM_DCS_CMD_ENTRY(0xC0, 0x00),
LCM_DCS_CMD_ENTRY(0xBC, 0x00),
LCM_DCS_CMD_ENTRY(0xB0, 0x07),
LCM_DCS_CMD_ENTRY(0xB1, 0x00),
LCM_DCS_CMD_ENTRY(0xB2, 0x02),
LCM_DCS_CMD_ENTRY(0xB3, 0x0F),
LCM_DCS_CMD_ENTRY(0xB4, 0x25),
LCM_DCS_CMD_ENTRY(0xB5, 0x39),
LCM_DCS_CMD_ENTRY(0xB6, 0x4E),
LCM_DCS_CMD_ENTRY(0xB7, 0x72),
LCM_DCS_CMD_ENTRY(0xB8, 0x97),
LCM_DCS_CMD_ENTRY(0xB9, 0xDC),
LCM_DCS_CMD_ENTRY(0xBA, 0x22),
LCM_DCS_CMD_ENTRY(0xBB, 0xA4),
LCM_DCS_CMD_ENTRY(0xBC, 0x2B),
LCM_DCS_CMD_ENTRY(0xBD, 0x2F),
LCM_DCS_CMD_ENTRY(0xBE, 0xA9),
LCM_DCS_CMD_ENTRY(0xBF, 0x25),
LCM_DCS_CMD_ENTRY(0xC0, 0x61),
LCM_DCS_CMD_ENTRY(0xC1, 0x97),
LCM_DCS_CMD_ENTRY(0xC2, 0xB2),
LCM_DCS_CMD_ENTRY(0xC3, 0xCD),
LCM_DCS_CMD_ENTRY(0xC4, 0xD9),
LCM_DCS_CMD_ENTRY(0xC5, 0xE7),
LCM_DCS_CMD_ENTRY(0xC6, 0xF4),
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
LCM_DCS_CMD_ENTRY(0xB0, 0x08),
LCM_DCS_CMD_ENTRY(0xB1, 0x04),
LCM_DCS_CMD_ENTRY(0xB2, 0x05),
LCM_DCS_CMD_ENTRY(0xB3, 0x11),
LCM_DCS_CMD_ENTRY(0xB4, 0x24),
LCM_DCS_CMD_ENTRY(0xB5, 0x39),
LCM_DCS_CMD_ENTRY(0xB6, 0x4F),
LCM_DCS_CMD_ENTRY(0xB7, 0x72),
LCM_DCS_CMD_ENTRY(0xB8, 0x98),
LCM_DCS_CMD_ENTRY(0xB9, 0xDC),
LCM_DCS_CMD_ENTRY(0xBA, 0x23),
LCM_DCS_CMD_ENTRY(0xBB, 0xA6),
LCM_DCS_CMD_ENTRY(0xBC, 0x2C),
LCM_DCS_CMD_ENTRY(0xBD, 0x30),
LCM_DCS_CMD_ENTRY(0xBE, 0xAA),
LCM_DCS_CMD_ENTRY(0xBF, 0x26),
LCM_DCS_CMD_ENTRY(0xC0, 0x62),
LCM_DCS_CMD_ENTRY(0xC1, 0x9B),
LCM_DCS_CMD_ENTRY(0xC2, 0xB5),
LCM_DCS_CMD_ENTRY(0xC3, 0xCF),
LCM_DCS_CMD_ENTRY(0xC4, 0xDB),
LCM_DCS_CMD_ENTRY(0xC5, 0xE8),
LCM_DCS_CMD_ENTRY(0xC6, 0xF5),
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
LCM_DCS_CMD_ENTRY(0xB0, 0x09),
LCM_DCS_CMD_ENTRY(0xB1, 0x04),
LCM_DCS_CMD_ENTRY(0xB2, 0x02),
LCM_DCS_CMD_ENTRY(0xB3, 0x16),
LCM_DCS_CMD_ENTRY(0xB4, 0x24),
LCM_DCS_CMD_ENTRY(0xB5, 0x3B),
LCM_DCS_CMD_ENTRY(0xB6, 0x4F),
LCM_DCS_CMD_ENTRY(0xB7, 0x73),
LCM_DCS_CMD_ENTRY(0xB8, 0x99),
LCM_DCS_CMD_ENTRY(0xB9, 0xE0),
LCM_DCS_CMD_ENTRY(0xBA, 0x26),
LCM_DCS_CMD_ENTRY(0xBB, 0xAD),
LCM_DCS_CMD_ENTRY(0xBC, 0x36),
LCM_DCS_CMD_ENTRY(0xBD, 0x3A),
LCM_DCS_CMD_ENTRY(0xBE, 0xAE),
LCM_DCS_CMD_ENTRY(0xBF, 0x2A),
LCM_DCS_CMD_ENTRY(0xC0, 0x66),
LCM_DCS_CMD_ENTRY(0xC1, 0x9E),
LCM_DCS_CMD_ENTRY(0xC2, 0xB8),
LCM_DCS_CMD_ENTRY(0xC3, 0xD1),
LCM_DCS_CMD_ENTRY(0xC4, 0xDD),
LCM_DCS_CMD_ENTRY(0xC5, 0xE9),
LCM_DCS_CMD_ENTRY(0xC6, 0xF6),
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
LCM_DCS_CMD_ENTRY(0xB0, 0x0A),
LCM_DCS_CMD_ENTRY(0xB1, 0x00),
LCM_DCS_CMD_ENTRY(0xB2, 0x02),
LCM_DCS_CMD_ENTRY(0xB3, 0x0F),
LCM_DCS_CMD_ENTRY(0xB4, 0x25),
LCM_DCS_CMD_ENTRY(0xB5, 0x39),
LCM_DCS_CMD_ENTRY(0xB6, 0x4E),
LCM_DCS_CMD_ENTRY(0xB7, 0x72),
LCM_DCS_CMD_ENTRY(0xB8, 0x97),
LCM_DCS_CMD_ENTRY(0xB9, 0xDC),
LCM_DCS_CMD_ENTRY(0xBA, 0x22),
LCM_DCS_CMD_ENTRY(0xBB, 0xA4),
LCM_DCS_CMD_ENTRY(0xBC, 0x2B),
LCM_DCS_CMD_ENTRY(0xBD, 0x2F),
LCM_DCS_CMD_ENTRY(0xBE, 0xA9),
LCM_DCS_CMD_ENTRY(0xBF, 0x25),
LCM_DCS_CMD_ENTRY(0xC0, 0x61),
LCM_DCS_CMD_ENTRY(0xC1, 0x97),
LCM_DCS_CMD_ENTRY(0xC2, 0xB2),
LCM_DCS_CMD_ENTRY(0xC3, 0xCD),
LCM_DCS_CMD_ENTRY(0xC4, 0xD9),
LCM_DCS_CMD_ENTRY(0xC5, 0xE7),
LCM_DCS_CMD_ENTRY(0xC6, 0xF4),
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
LCM_DCS_CMD_ENTRY(0xB0, 0x0B),
LCM_DCS_CMD_ENTRY(0xB1, 0x04),
LCM_DCS_CMD_ENTRY(0xB2, 0x05),
LCM_DCS_CMD_ENTRY(0xB3, 0x11),
LCM_DCS_CMD_ENTRY(0xB4, 0x24),
LCM_DCS_CMD_ENTRY(0xB5, 0x39),
LCM_DCS_CMD_ENTRY(0xB6, 0x4F),
LCM_DCS_CMD_ENTRY(0xB7, 0x72),
LCM_DCS_CMD_ENTRY(0xB8, 0x98),
LCM_DCS_CMD_ENTRY(0xB9, 0xDC),
LCM_DCS_CMD_ENTRY(0xBA, 0x23),
LCM_DCS_CMD_ENTRY(0xBB, 0xA6),
LCM_DCS_CMD_ENTRY(0xBC, 0x2C),
LCM_DCS_CMD_ENTRY(0xBD, 0x30),
LCM_DCS_CMD_ENTRY(0xBE, 0xAA),
LCM_DCS_CMD_ENTRY(0xBF, 0x26),
LCM_DCS_CMD_ENTRY(0xC0, 0x62),
LCM_DCS_CMD_ENTRY(0xC1, 0x9B),
LCM_DCS_CMD_ENTRY(0xC2, 0xB5),
LCM_DCS_CMD_ENTRY(0xC3, 0xCF),
LCM_DCS_CMD_ENTRY(0xC4, 0xDB),
LCM_DCS_CMD_ENTRY(0xC5, 0xE8),
LCM_DCS_CMD_ENTRY(0xC6, 0xF5),
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
LCM_DCS_CMD_ENTRY(0xB0, 0x0C),
LCM_DCS_CMD_ENTRY(0xB1, 0x04),
LCM_DCS_CMD_ENTRY(0xB2, 0x02),
LCM_DCS_CMD_ENTRY(0xB3, 0x16),
LCM_DCS_CMD_ENTRY(0xB4, 0x24),
LCM_DCS_CMD_ENTRY(0xB5, 0x3B),
LCM_DCS_CMD_ENTRY(0xB6, 0x4F),
LCM_DCS_CMD_ENTRY(0xB7, 0x73),
LCM_DCS_CMD_ENTRY(0xB8, 0x99),
LCM_DCS_CMD_ENTRY(0xB9, 0xE0),
LCM_DCS_CMD_ENTRY(0xBA, 0x26),
LCM_DCS_CMD_ENTRY(0xBB, 0xAD),
LCM_DCS_CMD_ENTRY(0xBC, 0x36),
LCM_DCS_CMD_ENTRY(0xBD, 0x3A),
LCM_DCS_CMD_ENTRY(0xBE, 0xAE),
LCM_DCS_CMD_ENTRY(0xBF, 0x2A),
LCM_DCS_CMD_ENTRY(0xC0, 0x66),
LCM_DCS_CMD_ENTRY(0xC1, 0x9E),
LCM_DCS_CMD_ENTRY(0xC2, 0xB8),
LCM_DCS_CMD_ENTRY(0xC3, 0xD1),
LCM_DCS_CMD_ENTRY(0xC4, 0xDD),
LCM_DCS_CMD_ENTRY(0xC5, 0xE9),
LCM_DCS_CMD_ENTRY(0xC6, 0xF6),
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
LCM_DCS_CMD_ENTRY(0xB0, 0x00),
LCM_DCS_CMD_ENTRY(0xB3, 0x08),
LCM_DCS_CMD_ENTRY(0xB0, 0x04),
LCM_DCS_CMD_ENTRY(0xB8, 0x68),
LCM_DELAY(150),
LCM_END_CMD,
};
/*
* Display timing. Totals match the Linux driver's
* boe_tv101wum_nl6_default_mode (htotal 1364, vtotal 1948), but the
* sync/back-porch split follows the coreboot EDID for this panel
* (hso 100 / hspw 24 / hbl-hso-hspw 40; vso 10 / vspw 4 / 14), the
* configuration proven to drive this panel on this device.
*/
static const struct display_timing boe_tv101wum_nl6_timing = {
.pixelclock.typ = 159425000,
.hactive.typ = 1200,
.hfront_porch.typ = 100,
.hback_porch.typ = 40,
.hsync_len.typ = 24,
.vactive.typ = 1920,
.vfront_porch.typ = 10,
.vback_porch.typ = 14,
.vsync_len.typ = 4,
};
struct boe_panel_priv {
struct udevice *avdd;
struct udevice *avee;
struct udevice *pp1800;
struct gpio_desc enable;
};
static int boe_panel_send_init_sequence(struct udevice *dev)
{
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
struct mipi_dsi_device *device = plat->device;
const u8 *p = boe_tv101wum_nl6_init_seq;
int ret;
while (*p != LCM_END_CMD) {
u8 type = *p++;
u8 len;
switch (type) {
case LCM_DELAY_CMD:
mdelay(*p++);
continue;
case LCM_DCS_CMD:
break;
default:
dev_err(dev, "unknown command type %u\n", type);
return -EINVAL;
}
len = *p++;
ret = mipi_dsi_dcs_write_buffer(device, p, len);
if (ret < 0)
return ret;
p += len;
}
return 0;
}
static int boe_panel_enable_backlight(struct udevice *dev)
{
struct boe_panel_priv *priv = dev_get_priv(dev);
int ret;
/*
* Power sequence from boe_panel_prepare() of the Linux driver
* (regulators on, reset pulse, manufacturer init commands). On
* the kukui family the supplies are already enabled by the boot
* firmware; enabling them again is a no-op.
*/
if (CONFIG_IS_ENABLED(DM_REGULATOR)) {
if (priv->avdd) {
ret = regulator_set_enable_if_allowed(priv->avdd,
true);
if (ret && ret != -ENOSYS)
return ret;
}
if (priv->avee) {
ret = regulator_set_enable_if_allowed(priv->avee,
true);
if (ret && ret != -ENOSYS)
return ret;
}
if (priv->pp1800) {
ret = regulator_set_enable_if_allowed(priv->pp1800,
true);
if (ret && ret != -ENOSYS)
return ret;
}
}
udelay(10000);
/*
* Reset pulse, following boe_panel_prepare() of the Linux driver:
* the "enable-gpios" pin is the active-low reset, so the sequence
* ends with it de-asserted (logical 1) before the init commands.
*/
if (dm_gpio_is_valid(&priv->enable)) {
dm_gpio_set_value(&priv->enable, 1);
udelay(1500);
dm_gpio_set_value(&priv->enable, 0);
udelay(1500);
dm_gpio_set_value(&priv->enable, 1);
}
udelay(8000);
ret = boe_panel_send_init_sequence(dev);
if (ret) {
dev_err(dev, "failed to send init sequence: %d\n", ret);
return ret;
}
/* Enable the backlight after the 150 ms delay of the sequence. */
if (CONFIG_IS_ENABLED(BACKLIGHT)) {
struct udevice *backlight;
if (!uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
"backlight", &backlight))
backlight_enable(backlight);
}
return 0;
}
static int boe_panel_get_display_timing(struct udevice *dev,
struct display_timing *timings)
{
memcpy(timings, &boe_tv101wum_nl6_timing, sizeof(*timings));
return 0;
}
static int boe_panel_of_to_plat(struct udevice *dev)
{
struct boe_panel_priv *priv = dev_get_priv(dev);
int ret;
if (CONFIG_IS_ENABLED(DM_REGULATOR)) {
ret = device_get_supply_regulator(dev, "avdd-supply",
&priv->avdd);
if (ret && ret != -ENOENT)
return ret;
ret = device_get_supply_regulator(dev, "avee-supply",
&priv->avee);
if (ret && ret != -ENOENT)
return ret;
ret = device_get_supply_regulator(dev, "pp1800-supply",
&priv->pp1800);
if (ret && ret != -ENOENT)
return ret;
}
ret = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable,
GPIOD_IS_OUT);
if (ret && ret != -ENOENT) {
dev_err(dev, "cannot get enable GPIO: %d\n", ret);
return ret;
}
/*
* Default to the reset released: a failed bring-up falls back to
* the firmware-handoff revival, which needs the panel alive.
*/
if (dm_gpio_is_valid(&priv->enable))
dm_gpio_set_value(&priv->enable, 1);
return 0;
}
static int boe_panel_probe(struct udevice *dev)
{
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
/* Fill the DSI data link characteristics for the host. */
plat->lanes = 4;
plat->format = MIPI_DSI_FMT_RGB888;
plat->mode_flags = MIPI_DSI_MODE_VIDEO |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_LPM;
return 0;
}
static const struct panel_ops boe_panel_ops = {
.enable_backlight = boe_panel_enable_backlight,
.get_display_timing = boe_panel_get_display_timing,
};
static const struct udevice_id boe_panel_ids[] = {
{ .compatible = "boe,tv101wum-nl6" },
{ }
};
U_BOOT_DRIVER(boe_panel) = {
.name = "boe_panel",
.id = UCLASS_PANEL,
.of_match = boe_panel_ids,
.ops = &boe_panel_ops,
.of_to_plat = boe_panel_of_to_plat,
.probe = boe_panel_probe,
.plat_auto = sizeof(struct mipi_dsi_panel_plat),
.priv_auto = sizeof(struct boe_panel_priv),
};
+1 -1
View File
@@ -95,7 +95,7 @@ static void vidconsole_newline(struct udevice *dev)
/* Check if we need to scroll the terminal */
if (vid_priv->rot % 2 ?
priv->ycur + priv->x_charsize > vid_priv->xsize :
priv->ycur + priv->y_charsize > vid_priv->xsize :
priv->ycur + priv->y_charsize > vid_priv->ysize) {
vidconsole_move_rows(dev, 0, rows, priv->rows - rows);
for (i = 0; i < rows; i++)
+21 -1
View File
@@ -194,6 +194,14 @@ static efi_status_t EFIAPI efi_cout_output_string(
}
pos = buf;
utf16_utf8_strcpy(&pos, string);
/*
* Output printed by U-Boot itself moves the video console cursor
* without updating the position tracked for EFI. Re-sync the console
* cursor before printing so text appears where the EFI spec places it.
*/
if (IS_ENABLED(CONFIG_VIDEO))
printf(ESC "[%d;%dH", (int)con->cursor_row + 1,
(int)con->cursor_column + 1);
puts(buf);
free(buf);
@@ -344,7 +352,8 @@ static int __maybe_unused query_vidconsole(int *rows, int *cols)
struct udevice *dev;
struct vidconsole_priv *priv;
if (!stdout_name || strncmp(stdout_name, "vidconsole", 10))
/* stdout may be a comma-separated list, e.g. "serial,vidconsole" */
if (!stdout_name || !strstr(stdout_name, "vidconsole"))
return -ENODEV;
stdout_dev = stdio_get_by_name("vidconsole");
if (!stdout_dev)
@@ -370,11 +379,14 @@ void efi_setup_console_size(void)
if (IS_ENABLED(CONFIG_VIDEO))
ret = query_vidconsole(&rows, &cols);
printf("EFI console: vidquery ret=%d, size %dx%d\n", ret, cols, rows);
if (ret) {
if (no_ansi)
ret = 0;
else
ret = query_console_serial(&rows, &cols);
printf("EFI console: serial fallback ret=%d, size %dx%d\n",
ret, cols, rows);
}
if (ret)
return;
@@ -399,6 +411,14 @@ void efi_setup_console_size(void)
efi_con_mode.max_mode = EFI_MAX_COUT_MODE;
efi_con_mode.mode = EFI_COUT_MODE_2;
}
/*
* EFI applications expect output to start at the top left of the
* screen, while the U-Boot prompt may have left the cursor anywhere.
*/
printf(ESC "[1;1H");
efi_con_mode.cursor_column = 0;
efi_con_mode.cursor_row = 0;
}
/**
+24 -1
View File
@@ -406,6 +406,21 @@ efi_status_t efi_add_memory_map(u64 start, u64 size, int memory_type)
* @must_be_allocated: return success if the page is allocated
* Return: status code
*/
/* TEMPORARY DIAGNOSTIC - DO NOT COMMIT */
static void efi_mem_dump_diag(void)
{
struct efi_mem_list *lmem;
int i = 0;
list_for_each_entry(lmem, &efi_mem, link) {
log_err("EFI map[%d]: type %d start %08llx pages %llu attr %llx\n",
i++, lmem->desc.type,
lmem->desc.physical_start,
lmem->desc.num_pages,
lmem->desc.attribute);
}
}
static efi_status_t efi_check_allocated(u64 addr, bool must_be_allocated)
{
struct efi_mem_list *item;
@@ -463,8 +478,12 @@ efi_status_t efi_allocate_pages(enum efi_allocate_type type,
/* Any page */
err = lmb_alloc_mem(LMB_MEM_ALLOC_ANY, EFI_PAGE_SIZE, &addr,
len, flags);
if (err)
if (err) {
log_err("EFI alloc %llu pages type %d: lmb_alloc_mem failed (%d)\n",
(u64)pages, memory_type, err);
efi_mem_dump_diag();
return EFI_OUT_OF_RESOURCES;
}
break;
case EFI_ALLOCATE_MAX_ADDRESS:
/* Max address */
@@ -493,6 +512,10 @@ efi_status_t efi_allocate_pages(enum efi_allocate_type type,
ret = efi_update_memory_map(efi_addr, pages, memory_type, true, false);
if (ret != EFI_SUCCESS) {
/* Map would overlap, bail out */
log_err("EFI alloc %llu pages type %d at %08llx: map update failed (%lu)\n",
(u64)pages, memory_type, efi_addr,
ret & ~EFI_ERROR_MASK);
efi_mem_dump_diag();
lmb_free(addr, (u64)pages << EFI_PAGE_SHIFT, flags);
unmap_sysmem((void *)(uintptr_t)efi_addr);
if (type == EFI_ALLOCATE_ADDRESS)